mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Refactor send page state management (#10965)
This commit is contained in:
parent
85f17831a2
commit
e17325c38a
@ -751,9 +751,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "የቅርብ ጊዜያት"
|
"message": "የቅርብ ጊዜያት"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "የተቀባይ አድራሻ"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "ፍለጋ፣ ለሕዝብ ክፍት የሆነ አድራሻ (0x), ወይም ENS"
|
"message": "ፍለጋ፣ ለሕዝብ ክፍት የሆነ አድራሻ (0x), ወይም ENS"
|
||||||
},
|
},
|
||||||
|
@ -747,9 +747,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "الحديث"
|
"message": "الحديث"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "عنوان المستلم"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "البحث، العنوان العام (0x)، أو ENS"
|
"message": "البحث، العنوان العام (0x)، أو ENS"
|
||||||
},
|
},
|
||||||
|
@ -750,9 +750,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "Скорошни"
|
"message": "Скорошни"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "Адрес на получателя"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "Търсене, публичен адрес (0x) или ENS"
|
"message": "Търсене, публичен адрес (0x) или ENS"
|
||||||
},
|
},
|
||||||
|
@ -754,9 +754,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "সাম্প্রতিকগুলি"
|
"message": "সাম্প্রতিকগুলি"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "প্রাপকের ঠিকানা"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "অনুসন্ধান, সার্বজনীন ঠিকানা (0x), বা ENS"
|
"message": "অনুসন্ধান, সার্বজনীন ঠিকানা (0x), বা ENS"
|
||||||
},
|
},
|
||||||
|
@ -732,9 +732,6 @@
|
|||||||
"readdToken": {
|
"readdToken": {
|
||||||
"message": "Pots tornar a afegir aquesta fitxa en el futur anant a \"Afegir fitxa\" al menu d'opcions dels teus comptes."
|
"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": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "Cerca, adreça pública (0x), o ENS"
|
"message": "Cerca, adreça pública (0x), o ENS"
|
||||||
},
|
},
|
||||||
|
@ -308,9 +308,6 @@
|
|||||||
"readdToken": {
|
"readdToken": {
|
||||||
"message": "Tento token můžete v budoucnu přidat zpět s „Přidat token“ v nastavení účtu."
|
"message": "Tento token můžete v budoucnu přidat zpět s „Přidat token“ v nastavení účtu."
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "Adresa příjemce"
|
|
||||||
},
|
|
||||||
"reject": {
|
"reject": {
|
||||||
"message": "Odmítnout"
|
"message": "Odmítnout"
|
||||||
},
|
},
|
||||||
|
@ -735,9 +735,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "Seneste"
|
"message": "Seneste"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "Modtagerens adresse"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "Søg, offentlig adresse (0x) eller ENS"
|
"message": "Søg, offentlig adresse (0x) eller ENS"
|
||||||
},
|
},
|
||||||
|
@ -723,9 +723,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "Letzte"
|
"message": "Letzte"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "Empfängeradresse"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "Suchen, öffentliche Adresse (0x) oder ENS"
|
"message": "Suchen, öffentliche Adresse (0x) oder ENS"
|
||||||
},
|
},
|
||||||
|
@ -751,9 +751,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "Πρόσφατα"
|
"message": "Πρόσφατα"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "Διεύθυνση Παραλήπτη"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "Αναζήτηση, δημόσια διεύθυνση (0x) ή ENS"
|
"message": "Αναζήτηση, δημόσια διεύθυνση (0x) ή ENS"
|
||||||
},
|
},
|
||||||
|
@ -650,12 +650,21 @@
|
|||||||
"message": "The endpoint returned a different chain ID: $1",
|
"message": "The endpoint returned a different chain ID: $1",
|
||||||
"description": "$1 is the return value of eth_chainId from an RPC endpoint"
|
"description": "$1 is the return value of eth_chainId from an RPC endpoint"
|
||||||
},
|
},
|
||||||
|
"ensIllegalCharacter": {
|
||||||
|
"message": "Illegal Character for ENS."
|
||||||
|
},
|
||||||
"ensNotFoundOnCurrentNetwork": {
|
"ensNotFoundOnCurrentNetwork": {
|
||||||
"message": "ENS name not found on the current network. Try switching to Ethereum Mainnet."
|
"message": "ENS name not found on the current network. Try switching to Ethereum Mainnet."
|
||||||
},
|
},
|
||||||
|
"ensNotSupportedOnNetwork": {
|
||||||
|
"message": "Network does not support ENS"
|
||||||
|
},
|
||||||
"ensRegistrationError": {
|
"ensRegistrationError": {
|
||||||
"message": "Error in ENS name registration"
|
"message": "Error in ENS name registration"
|
||||||
},
|
},
|
||||||
|
"ensUnknownError": {
|
||||||
|
"message": "ENS Lookup failed."
|
||||||
|
},
|
||||||
"enterAnAlias": {
|
"enterAnAlias": {
|
||||||
"message": "Enter an alias"
|
"message": "Enter an alias"
|
||||||
},
|
},
|
||||||
@ -1451,9 +1460,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "Recents"
|
"message": "Recents"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "Recipient Address"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "Search, public address (0x), or ENS"
|
"message": "Search, public address (0x), or ENS"
|
||||||
},
|
},
|
||||||
|
@ -1411,9 +1411,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "Recientes"
|
"message": "Recientes"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "Dirección del destinatario"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "Búsqueda, dirección pública (0x) o ENS"
|
"message": "Búsqueda, dirección pública (0x) o ENS"
|
||||||
},
|
},
|
||||||
|
@ -1419,9 +1419,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "Recientes"
|
"message": "Recientes"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "Dirección del destinatario"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "Búsqueda, dirección pública (0x) o ENS"
|
"message": "Búsqueda, dirección pública (0x) o ENS"
|
||||||
},
|
},
|
||||||
|
@ -744,9 +744,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "Hiljutised"
|
"message": "Hiljutised"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "Saaja aadress"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "Otsing, avalik aadress (0x) või ENS"
|
"message": "Otsing, avalik aadress (0x) või ENS"
|
||||||
},
|
},
|
||||||
|
@ -754,9 +754,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "واپسین"
|
"message": "واپسین"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "آدرس دریافت کننده"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "جستجو، آدرس عمومی (0x)، یا ENS"
|
"message": "جستجو، آدرس عمومی (0x)، یا ENS"
|
||||||
},
|
},
|
||||||
|
@ -751,9 +751,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "Viimeaikaiset"
|
"message": "Viimeaikaiset"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "Vastaanottajan osoite"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "Haku, julkinen osoite (0x) tai ENS"
|
"message": "Haku, julkinen osoite (0x) tai ENS"
|
||||||
},
|
},
|
||||||
|
@ -678,9 +678,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "Kamakailan"
|
"message": "Kamakailan"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "Address ng Recipient"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "Maghanap, pampublikong address (0x), o ENS"
|
"message": "Maghanap, pampublikong address (0x), o ENS"
|
||||||
},
|
},
|
||||||
|
@ -736,9 +736,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "Récents"
|
"message": "Récents"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "Adresse du destinataire"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "Recherche, adresse publique (0x) ou ENS"
|
"message": "Recherche, adresse publique (0x) ou ENS"
|
||||||
},
|
},
|
||||||
|
@ -751,9 +751,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "אחרונים"
|
"message": "אחרונים"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "כתובת הנמען"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "חיפוש, כתובת ציבורית (0x), או ENS"
|
"message": "חיפוש, כתובת ציבורית (0x), או ENS"
|
||||||
},
|
},
|
||||||
|
@ -1411,9 +1411,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "हाल ही के"
|
"message": "हाल ही के"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "प्राप्तकर्ता का पता"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "खोज, सार्वजनिक पता (0x) या ENS"
|
"message": "खोज, सार्वजनिक पता (0x) या ENS"
|
||||||
},
|
},
|
||||||
|
@ -285,9 +285,6 @@
|
|||||||
"readdToken": {
|
"readdToken": {
|
||||||
"message": "आप अपने खाता विकल्प मेनू में .टोकन जोड़ें. पर जाकर भविष्य में इस टोकन को वापस जोड़ सकते हैं।"
|
"message": "आप अपने खाता विकल्प मेनू में .टोकन जोड़ें. पर जाकर भविष्य में इस टोकन को वापस जोड़ सकते हैं।"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "प्राप्तकर्ता पता"
|
|
||||||
},
|
|
||||||
"reject": {
|
"reject": {
|
||||||
"message": "अस्वीकार"
|
"message": "अस्वीकार"
|
||||||
},
|
},
|
||||||
|
@ -747,9 +747,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "Nedavno"
|
"message": "Nedavno"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "Adresa primatelja"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "Pretraži, javne adrese (0x) ili ENS"
|
"message": "Pretraži, javne adrese (0x) ili ENS"
|
||||||
},
|
},
|
||||||
|
@ -450,9 +450,6 @@
|
|||||||
"readdToken": {
|
"readdToken": {
|
||||||
"message": "Ou ka ajoute token sa aprè sa ankò ou prale nan \"Ajoute token\" nan opsyon meni kont ou an."
|
"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": {
|
"reject": {
|
||||||
"message": "Rejte"
|
"message": "Rejte"
|
||||||
},
|
},
|
||||||
|
@ -747,9 +747,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "Legutóbbiak"
|
"message": "Legutóbbiak"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "Címzett címe"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "Keresés, nyilvános cím (0x) vagy ENS"
|
"message": "Keresés, nyilvános cím (0x) vagy ENS"
|
||||||
},
|
},
|
||||||
|
@ -1411,9 +1411,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "Terkini"
|
"message": "Terkini"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "Alamat Penerima"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "Cari, alamat publik (0x), atau ENS"
|
"message": "Cari, alamat publik (0x), atau ENS"
|
||||||
},
|
},
|
||||||
|
@ -1201,9 +1201,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "Recenti"
|
"message": "Recenti"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "Indirizzo Destinatario"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "Ricerca, indirizzo pubblico (0x) o ENS"
|
"message": "Ricerca, indirizzo pubblico (0x) o ENS"
|
||||||
},
|
},
|
||||||
|
@ -1411,9 +1411,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "最近"
|
"message": "最近"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "受信者のアドレス"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "検索、パブリック アドレス (0x)、または ENS"
|
"message": "検索、パブリック アドレス (0x)、または ENS"
|
||||||
},
|
},
|
||||||
|
@ -754,9 +754,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "ಇತ್ತೀಚಿನವುಗಳು"
|
"message": "ಇತ್ತೀಚಿನವುಗಳು"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "ಸ್ವೀಕರಿಸುವವರ ವಿಳಾಸ"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "ಸಾರ್ವಜನಿಕ ವಿಳಾಸ (0x) ಅಥವಾ ENS ಹುಡುಕಿ"
|
"message": "ಸಾರ್ವಜನಿಕ ವಿಳಾಸ (0x) ಅಥವಾ ENS ಹುಡುಕಿ"
|
||||||
},
|
},
|
||||||
|
@ -1415,9 +1415,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "최근"
|
"message": "최근"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "수신인 주소"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "검색, 공개 주소(0x) 또는 ENS"
|
"message": "검색, 공개 주소(0x) 또는 ENS"
|
||||||
},
|
},
|
||||||
|
@ -754,9 +754,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "Naujausi"
|
"message": "Naujausi"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "Gavėjo adresas"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "Ieška, viešieji adresai (0x) arba ENS"
|
"message": "Ieška, viešieji adresai (0x) arba ENS"
|
||||||
},
|
},
|
||||||
|
@ -750,9 +750,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "Nesenie"
|
"message": "Nesenie"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "Saņēmēja adrese"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "Meklēšana, publiskā adrese (0x) vai ENS"
|
"message": "Meklēšana, publiskā adrese (0x) vai ENS"
|
||||||
},
|
},
|
||||||
|
@ -731,9 +731,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "Baru-baru ini"
|
"message": "Baru-baru ini"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "Alamat Penerima"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "Cari, alamat awam (0x), atau ENS"
|
"message": "Cari, alamat awam (0x), atau ENS"
|
||||||
},
|
},
|
||||||
|
@ -272,9 +272,6 @@
|
|||||||
"readdToken": {
|
"readdToken": {
|
||||||
"message": "U kunt dit token in de toekomst weer toevoegen door naar \"Token toevoegen\" te gaan in het menu met accountopties."
|
"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": {
|
"reject": {
|
||||||
"message": "Afwijzen"
|
"message": "Afwijzen"
|
||||||
},
|
},
|
||||||
|
@ -741,9 +741,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "Nylige"
|
"message": "Nylige"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "Mottakeradresse"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "Søk, offentlig adresse (0x) eller ENS"
|
"message": "Søk, offentlig adresse (0x) eller ENS"
|
||||||
},
|
},
|
||||||
|
@ -1419,9 +1419,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "Mga Kamakailan"
|
"message": "Mga Kamakailan"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "Address ng Tatanggap"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "Maghanap, pampublikong address (0x), o ENS"
|
"message": "Maghanap, pampublikong address (0x), o ENS"
|
||||||
},
|
},
|
||||||
|
@ -748,9 +748,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "Ostatnie"
|
"message": "Ostatnie"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "Adres odbiorcy"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "Szukaj, adres publiczny (0x) lub ENS"
|
"message": "Szukaj, adres publiczny (0x) lub ENS"
|
||||||
},
|
},
|
||||||
|
@ -282,9 +282,6 @@
|
|||||||
"readdToken": {
|
"readdToken": {
|
||||||
"message": "Pode adicionar este token de novo clicando na opção “Adicionar token” no menu de opções da sua conta."
|
"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": {
|
"reject": {
|
||||||
"message": "Rejeitar"
|
"message": "Rejeitar"
|
||||||
},
|
},
|
||||||
|
@ -1405,9 +1405,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "Recentes"
|
"message": "Recentes"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "Endereço do destinatário"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "Busca, endereço público (0x) ou ENS"
|
"message": "Busca, endereço público (0x) ou ENS"
|
||||||
},
|
},
|
||||||
|
@ -741,9 +741,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "Recente"
|
"message": "Recente"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "Adresă destinatar"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "Căutare, adresa publică (0x) sau ENS"
|
"message": "Căutare, adresa publică (0x) sau ENS"
|
||||||
},
|
},
|
||||||
|
@ -1411,9 +1411,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "Недавние"
|
"message": "Недавние"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "Адрес получателя"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "Поиск, публичный адрес (0x) или ENS"
|
"message": "Поиск, публичный адрес (0x) или ENS"
|
||||||
},
|
},
|
||||||
|
@ -723,9 +723,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "Posledné"
|
"message": "Posledné"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "Adresa příjemce"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "Vyhľadávať verejnú adresu (0x) alebo ENS"
|
"message": "Vyhľadávať verejnú adresu (0x) alebo ENS"
|
||||||
},
|
},
|
||||||
|
@ -742,9 +742,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "Nedavno"
|
"message": "Nedavno"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "Prejemnikov naslov"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "Iskanje, javni naslov (0x) ali ENS"
|
"message": "Iskanje, javni naslov (0x) ali ENS"
|
||||||
},
|
},
|
||||||
|
@ -745,9 +745,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "Skorašnje"
|
"message": "Skorašnje"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "Adresa primaoca"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "Pretraga, javna adresa (0x) ili ENS"
|
"message": "Pretraga, javna adresa (0x) ili ENS"
|
||||||
},
|
},
|
||||||
|
@ -738,9 +738,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "Senaste"
|
"message": "Senaste"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "Mottagaradress"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "Sök, allmän adress (0x) eller ENS"
|
"message": "Sök, allmän adress (0x) eller ENS"
|
||||||
},
|
},
|
||||||
|
@ -732,9 +732,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "Za hivi karibuni"
|
"message": "Za hivi karibuni"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "Anwani ya Mpokeaji"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "Tafuta, anwani za umma (0x), au ENS"
|
"message": "Tafuta, anwani za umma (0x), au ENS"
|
||||||
},
|
},
|
||||||
|
@ -372,9 +372,6 @@
|
|||||||
"readdToken": {
|
"readdToken": {
|
||||||
"message": "உங்கள் கணக்கு விருப்பங்கள் மெனுவில் \"டோக்கனைச் சேர்\" என்பதன் மூலம் நீங்கள் எதிர்காலத்தில் இந்த டோக்கனை மீண்டும் சேர்க்கலாம்."
|
"message": "உங்கள் கணக்கு விருப்பங்கள் மெனுவில் \"டோக்கனைச் சேர்\" என்பதன் மூலம் நீங்கள் எதிர்காலத்தில் இந்த டோக்கனை மீண்டும் சேர்க்கலாம்."
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "பெறுநர் முகவரி"
|
|
||||||
},
|
|
||||||
"reject": {
|
"reject": {
|
||||||
"message": "நிராகரி"
|
"message": "நிராகரி"
|
||||||
},
|
},
|
||||||
|
@ -375,9 +375,6 @@
|
|||||||
"readdToken": {
|
"readdToken": {
|
||||||
"message": "คุณสามารถเพิ่มโทเค็นนี้ในอนาคตได้โดยไปที่ “เพิ่มโทเค็น” ในเมนูตัวเลือกบัญชีของคุณ"
|
"message": "คุณสามารถเพิ่มโทเค็นนี้ในอนาคตได้โดยไปที่ “เพิ่มโทเค็น” ในเมนูตัวเลือกบัญชีของคุณ"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "แอดแดรสผู้รับ"
|
|
||||||
},
|
|
||||||
"reject": {
|
"reject": {
|
||||||
"message": "ปฏิเสธ"
|
"message": "ปฏิเสธ"
|
||||||
},
|
},
|
||||||
|
@ -1192,9 +1192,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "Mga Kamakailan"
|
"message": "Mga Kamakailan"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "Address ng Tatanggap"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "Maghanap, pampublikong address (0x), o ENS"
|
"message": "Maghanap, pampublikong address (0x), o ENS"
|
||||||
},
|
},
|
||||||
|
@ -324,9 +324,6 @@
|
|||||||
"readdToken": {
|
"readdToken": {
|
||||||
"message": "Gelecekte Bu jetonu hesap seçenekleri menüsünde “Jeton ekle”'ye giderek geri ekleyebilirsiniz."
|
"message": "Gelecekte Bu jetonu hesap seçenekleri menüsünde “Jeton ekle”'ye giderek geri ekleyebilirsiniz."
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "Alıcı adresi"
|
|
||||||
},
|
|
||||||
"reject": {
|
"reject": {
|
||||||
"message": "Reddetmek"
|
"message": "Reddetmek"
|
||||||
},
|
},
|
||||||
|
@ -754,9 +754,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "Останні"
|
"message": "Останні"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "Адреса отримувача"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "Пошук, публічна адреса (0x), або ENS"
|
"message": "Пошук, публічна адреса (0x), або ENS"
|
||||||
},
|
},
|
||||||
|
@ -1411,9 +1411,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "Gần đây"
|
"message": "Gần đây"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "Địa chỉ người nhận"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "Tìm kiếm, địa chỉ công khai (0x) hoặc ENS"
|
"message": "Tìm kiếm, địa chỉ công khai (0x) hoặc ENS"
|
||||||
},
|
},
|
||||||
|
@ -1195,9 +1195,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "最近记录"
|
"message": "最近记录"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "接收地址"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "查找、公用地址 (0x) 或 ENS"
|
"message": "查找、公用地址 (0x) 或 ENS"
|
||||||
},
|
},
|
||||||
|
@ -751,9 +751,6 @@
|
|||||||
"recents": {
|
"recents": {
|
||||||
"message": "最近"
|
"message": "最近"
|
||||||
},
|
},
|
||||||
"recipientAddress": {
|
|
||||||
"message": "接收位址"
|
|
||||||
},
|
|
||||||
"recipientAddressPlaceholder": {
|
"recipientAddressPlaceholder": {
|
||||||
"message": "搜尋,公開地址 (0x),或 ENS"
|
"message": "搜尋,公開地址 (0x),或 ENS"
|
||||||
},
|
},
|
||||||
|
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 },
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
@ -10,7 +10,7 @@ import InfoIcon from '../../ui/icon/info-icon.component';
|
|||||||
import Button from '../../ui/button';
|
import Button from '../../ui/button';
|
||||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||||
import { useMetricEvent } from '../../../hooks/useMetricEvent';
|
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 { SEND_ROUTE } from '../../../helpers/constants/routes';
|
||||||
import { SEVERITIES } from '../../../helpers/constants/design-system';
|
import { SEVERITIES } from '../../../helpers/constants/design-system';
|
||||||
|
|
||||||
@ -69,13 +69,17 @@ const AssetListItem = ({
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
sendTokenEvent();
|
sendTokenEvent();
|
||||||
dispatch(
|
dispatch(
|
||||||
updateSendToken({
|
updateSendAsset({
|
||||||
|
type: ASSET_TYPES.TOKEN,
|
||||||
|
details: {
|
||||||
address: tokenAddress,
|
address: tokenAddress,
|
||||||
decimals: tokenDecimals,
|
decimals: tokenDecimals,
|
||||||
symbol: tokenSymbol,
|
symbol: tokenSymbol,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
);
|
).then(() => {
|
||||||
history.push(SEND_ROUTE);
|
history.push(SEND_ROUTE);
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('sendSpecifiedTokens', [tokenSymbol])}
|
{t('sendSpecifiedTokens', [tokenSymbol])}
|
||||||
|
@ -9,10 +9,10 @@ import {
|
|||||||
} from '../../../../ducks/gas/gas.duck';
|
} from '../../../../ducks/gas/gas.duck';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
hideGasButtonGroup,
|
useCustomGas,
|
||||||
setGasLimit,
|
updateGasLimit,
|
||||||
setGasPrice,
|
updateGasPrice,
|
||||||
} from '../../../../ducks/send/send.duck';
|
} from '../../../../ducks/send';
|
||||||
|
|
||||||
let mapDispatchToProps;
|
let mapDispatchToProps;
|
||||||
let mergeProps;
|
let mergeProps;
|
||||||
@ -32,8 +32,6 @@ jest.mock('../../../../selectors', () => ({
|
|||||||
`mockRenderableBasicEstimateData:${Object.keys(s).length}`,
|
`mockRenderableBasicEstimateData:${Object.keys(s).length}`,
|
||||||
getDefaultActiveButtonIndex: (a, b) => a + b,
|
getDefaultActiveButtonIndex: (a, b) => a + b,
|
||||||
getCurrentEthBalance: (state) => state.metamask.balance || '0x0',
|
getCurrentEthBalance: (state) => state.metamask.balance || '0x0',
|
||||||
getSendToken: () => null,
|
|
||||||
getTokenBalance: (state) => state.send.tokenBalance || '0x0',
|
|
||||||
getCustomGasPrice: (state) => state.gas.customData.price || '0x0',
|
getCustomGasPrice: (state) => state.gas.customData.price || '0x0',
|
||||||
getCustomGasLimit: (state) => state.gas.customData.limit || '0x0',
|
getCustomGasLimit: (state) => state.gas.customData.limit || '0x0',
|
||||||
getCurrentCurrency: jest.fn().mockReturnValue('usd'),
|
getCurrentCurrency: jest.fn().mockReturnValue('usd'),
|
||||||
@ -57,11 +55,15 @@ jest.mock('../../../../ducks/gas/gas.duck', () => ({
|
|||||||
resetCustomData: jest.fn(),
|
resetCustomData: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('../../../../ducks/send/send.duck', () => ({
|
jest.mock('../../../../ducks/send', () => {
|
||||||
hideGasButtonGroup: jest.fn(),
|
const { ASSET_TYPES } = jest.requireActual('../../../../ducks/send');
|
||||||
setGasLimit: jest.fn(),
|
return {
|
||||||
setGasPrice: jest.fn(),
|
useCustomGas: jest.fn(),
|
||||||
}));
|
updateGasLimit: jest.fn(),
|
||||||
|
updateGasPrice: jest.fn(),
|
||||||
|
getSendAsset: jest.fn(() => ({ type: ASSET_TYPES.NATIVE })),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
require('./gas-modal-page-container.container');
|
require('./gas-modal-page-container.container');
|
||||||
|
|
||||||
@ -79,11 +81,11 @@ describe('gas-modal-page-container container', () => {
|
|||||||
dispatchSpy.resetHistory();
|
dispatchSpy.resetHistory();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('hideGasButtonGroup()', () => {
|
describe('useCustomGas()', () => {
|
||||||
it('should dispatch a hideGasButtonGroup action', () => {
|
it('should dispatch a useCustomGas action', () => {
|
||||||
mapDispatchToPropsObject.hideGasButtonGroup();
|
mapDispatchToPropsObject.useCustomGas();
|
||||||
expect(dispatchSpy.calledOnce).toStrictEqual(true);
|
expect(dispatchSpy.calledOnce).toStrictEqual(true);
|
||||||
expect(hideGasButtonGroup).toHaveBeenCalled();
|
expect(useCustomGas).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -126,13 +128,13 @@ describe('gas-modal-page-container container', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('setGasData()', () => {
|
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');
|
mapDispatchToPropsObject.setGasData('ffff', 'aaaa');
|
||||||
expect(dispatchSpy.calledTwice).toStrictEqual(true);
|
expect(dispatchSpy.calledTwice).toStrictEqual(true);
|
||||||
expect(setGasPrice).toHaveBeenCalled();
|
expect(updateGasPrice).toHaveBeenCalled();
|
||||||
expect(setGasLimit).toHaveBeenCalled();
|
expect(updateGasLimit).toHaveBeenCalled();
|
||||||
expect(setGasLimit).toHaveBeenCalledWith('ffff');
|
expect(updateGasLimit).toHaveBeenCalledWith('ffff');
|
||||||
expect(setGasPrice).toHaveBeenCalledWith('aaaa');
|
expect(updateGasPrice).toHaveBeenCalledWith('aaaa');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -165,7 +167,7 @@ describe('gas-modal-page-container container', () => {
|
|||||||
};
|
};
|
||||||
dispatchProps = {
|
dispatchProps = {
|
||||||
updateCustomGasPrice: sinon.spy(),
|
updateCustomGasPrice: sinon.spy(),
|
||||||
hideGasButtonGroup: sinon.spy(),
|
useCustomGas: sinon.spy(),
|
||||||
setGasData: sinon.spy(),
|
setGasData: sinon.spy(),
|
||||||
updateConfirmTxGasAndCalculate: sinon.spy(),
|
updateConfirmTxGasAndCalculate: sinon.spy(),
|
||||||
someOtherDispatchProp: sinon.spy(),
|
someOtherDispatchProp: sinon.spy(),
|
||||||
@ -194,7 +196,7 @@ describe('gas-modal-page-container container', () => {
|
|||||||
dispatchProps.updateConfirmTxGasAndCalculate.callCount,
|
dispatchProps.updateConfirmTxGasAndCalculate.callCount,
|
||||||
).toStrictEqual(0);
|
).toStrictEqual(0);
|
||||||
expect(dispatchProps.setGasData.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);
|
expect(dispatchProps.hideModal.callCount).toStrictEqual(0);
|
||||||
|
|
||||||
result.onSubmit();
|
result.onSubmit();
|
||||||
@ -203,7 +205,7 @@ describe('gas-modal-page-container container', () => {
|
|||||||
dispatchProps.updateConfirmTxGasAndCalculate.callCount,
|
dispatchProps.updateConfirmTxGasAndCalculate.callCount,
|
||||||
).toStrictEqual(1);
|
).toStrictEqual(1);
|
||||||
expect(dispatchProps.setGasData.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(1);
|
expect(dispatchProps.hideModal.callCount).toStrictEqual(1);
|
||||||
|
|
||||||
expect(dispatchProps.updateCustomGasPrice.callCount).toStrictEqual(0);
|
expect(dispatchProps.updateCustomGasPrice.callCount).toStrictEqual(0);
|
||||||
@ -238,7 +240,7 @@ describe('gas-modal-page-container container', () => {
|
|||||||
dispatchProps.updateConfirmTxGasAndCalculate.callCount,
|
dispatchProps.updateConfirmTxGasAndCalculate.callCount,
|
||||||
).toStrictEqual(0);
|
).toStrictEqual(0);
|
||||||
expect(dispatchProps.setGasData.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);
|
expect(dispatchProps.cancelAndClose.callCount).toStrictEqual(0);
|
||||||
|
|
||||||
result.onSubmit('mockNewLimit', 'mockNewPrice');
|
result.onSubmit('mockNewLimit', 'mockNewPrice');
|
||||||
@ -251,7 +253,7 @@ describe('gas-modal-page-container container', () => {
|
|||||||
'mockNewLimit',
|
'mockNewLimit',
|
||||||
'mockNewPrice',
|
'mockNewPrice',
|
||||||
]);
|
]);
|
||||||
expect(dispatchProps.hideGasButtonGroup.callCount).toStrictEqual(1);
|
expect(dispatchProps.useCustomGas.callCount).toStrictEqual(1);
|
||||||
expect(dispatchProps.cancelAndClose.callCount).toStrictEqual(1);
|
expect(dispatchProps.cancelAndClose.callCount).toStrictEqual(1);
|
||||||
|
|
||||||
expect(dispatchProps.updateCustomGasPrice.callCount).toStrictEqual(0);
|
expect(dispatchProps.updateCustomGasPrice.callCount).toStrictEqual(0);
|
||||||
@ -278,7 +280,7 @@ describe('gas-modal-page-container container', () => {
|
|||||||
dispatchProps.updateConfirmTxGasAndCalculate.callCount,
|
dispatchProps.updateConfirmTxGasAndCalculate.callCount,
|
||||||
).toStrictEqual(0);
|
).toStrictEqual(0);
|
||||||
expect(dispatchProps.setGasData.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.cancelAndClose.callCount).toStrictEqual(1);
|
||||||
|
|
||||||
expect(dispatchProps.createSpeedUpTransaction.callCount).toStrictEqual(1);
|
expect(dispatchProps.createSpeedUpTransaction.callCount).toStrictEqual(1);
|
||||||
|
@ -14,19 +14,21 @@ import {
|
|||||||
fetchBasicGasEstimates,
|
fetchBasicGasEstimates,
|
||||||
} from '../../../../ducks/gas/gas.duck';
|
} from '../../../../ducks/gas/gas.duck';
|
||||||
import {
|
import {
|
||||||
hideGasButtonGroup,
|
getSendMaxModeState,
|
||||||
setGasLimit,
|
getGasLimit,
|
||||||
setGasPrice,
|
getGasPrice,
|
||||||
setGasTotal,
|
getSendAmount,
|
||||||
updateSendAmount,
|
updateGasLimit,
|
||||||
updateSendErrors,
|
updateGasPrice,
|
||||||
} from '../../../../ducks/send/send.duck';
|
useCustomGas,
|
||||||
|
getSendAsset,
|
||||||
|
ASSET_TYPES,
|
||||||
|
} from '../../../../ducks/send';
|
||||||
import {
|
import {
|
||||||
conversionRateSelector as getConversionRate,
|
conversionRateSelector as getConversionRate,
|
||||||
getCurrentCurrency,
|
getCurrentCurrency,
|
||||||
getCurrentEthBalance,
|
getCurrentEthBalance,
|
||||||
getIsMainnet,
|
getIsMainnet,
|
||||||
getSendToken,
|
|
||||||
getPreferences,
|
getPreferences,
|
||||||
getIsTestnet,
|
getIsTestnet,
|
||||||
getBasicGasEstimateLoadingStatus,
|
getBasicGasEstimateLoadingStatus,
|
||||||
@ -35,8 +37,6 @@ import {
|
|||||||
getDefaultActiveButtonIndex,
|
getDefaultActiveButtonIndex,
|
||||||
getRenderableBasicEstimateData,
|
getRenderableBasicEstimateData,
|
||||||
isCustomPriceSafe,
|
isCustomPriceSafe,
|
||||||
getTokenBalance,
|
|
||||||
getSendMaxModeState,
|
|
||||||
isCustomPriceSafeForCustomNetwork,
|
isCustomPriceSafeForCustomNetwork,
|
||||||
getAveragePriceEstimateInHexWEI,
|
getAveragePriceEstimateInHexWEI,
|
||||||
isCustomPriceExcessive,
|
isCustomPriceExcessive,
|
||||||
@ -57,16 +57,15 @@ import {
|
|||||||
isBalanceSufficient,
|
isBalanceSufficient,
|
||||||
} from '../../../../pages/send/send.utils';
|
} from '../../../../pages/send/send.utils';
|
||||||
import { MIN_GAS_LIMIT_DEC } from '../../../../pages/send/send.constants';
|
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 { TRANSACTION_STATUSES } from '../../../../../shared/constants/transaction';
|
||||||
import { GAS_LIMITS } from '../../../../../shared/constants/gas';
|
import { GAS_LIMITS } from '../../../../../shared/constants/gas';
|
||||||
import GasModalPageContainer from './gas-modal-page-container.component';
|
import GasModalPageContainer from './gas-modal-page-container.component';
|
||||||
|
|
||||||
const mapStateToProps = (state, ownProps) => {
|
const mapStateToProps = (state, ownProps) => {
|
||||||
const {
|
const gasLimit = getGasLimit(state);
|
||||||
metamask: { currentNetworkTxList },
|
const gasPrice = getGasPrice(state);
|
||||||
send,
|
const amount = getSendAmount(state);
|
||||||
} = state;
|
const { currentNetworkTxList } = state.metamask;
|
||||||
const { modalState: { props: modalProps } = {} } = state.appState.modal || {};
|
const { modalState: { props: modalProps } = {} } = state.appState.modal || {};
|
||||||
const { txData = {} } = modalProps || {};
|
const { txData = {} } = modalProps || {};
|
||||||
const { transaction = {}, onSubmit } = ownProps;
|
const { transaction = {}, onSubmit } = ownProps;
|
||||||
@ -74,15 +73,15 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
({ id }) => id === (transaction.id || txData.id),
|
({ id }) => id === (transaction.id || txData.id),
|
||||||
);
|
);
|
||||||
const buttonDataLoading = getBasicGasEstimateLoadingStatus(state);
|
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
|
// a "default" txParams is used during the send flow, since the transaction doesn't exist yet in that case
|
||||||
const txParams = selectedTransaction?.txParams
|
const txParams = selectedTransaction?.txParams
|
||||||
? selectedTransaction.txParams
|
? selectedTransaction.txParams
|
||||||
: {
|
: {
|
||||||
gas: send.gasLimit || GAS_LIMITS.SIMPLE,
|
gas: gasLimit || GAS_LIMITS.SIMPLE,
|
||||||
gasPrice: send.gasPrice || getAveragePriceEstimateInHexWEI(state, true),
|
gasPrice: gasPrice || getAveragePriceEstimateInHexWEI(state, true),
|
||||||
value: sendToken ? '0x0' : send.amount,
|
value: asset.type === ASSET_TYPES.TOKEN ? '0x0' : amount,
|
||||||
};
|
};
|
||||||
|
|
||||||
const { gasPrice: currentGasPrice, gas: currentGasLimit } = txParams;
|
const { gasPrice: currentGasPrice, gas: currentGasLimit } = txParams;
|
||||||
@ -120,16 +119,15 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
const isMainnet = getIsMainnet(state);
|
const isMainnet = getIsMainnet(state);
|
||||||
const showFiat = Boolean(isMainnet || showFiatInTestnets);
|
const showFiat = Boolean(isMainnet || showFiatInTestnets);
|
||||||
|
|
||||||
const isSendTokenSet = Boolean(sendToken);
|
|
||||||
const isTestnet = getIsTestnet(state);
|
const isTestnet = getIsTestnet(state);
|
||||||
|
|
||||||
const newTotalEth =
|
const newTotalEth =
|
||||||
maxModeOn && !isSendTokenSet
|
maxModeOn && asset.type === ASSET_TYPES.NATIVE
|
||||||
? sumHexWEIsToRenderableEth([balance, '0x0'])
|
? sumHexWEIsToRenderableEth([balance, '0x0'])
|
||||||
: sumHexWEIsToRenderableEth([value, customGasTotal]);
|
: sumHexWEIsToRenderableEth([value, customGasTotal]);
|
||||||
|
|
||||||
const sendAmount =
|
const sendAmount =
|
||||||
maxModeOn && !isSendTokenSet
|
maxModeOn && asset.type === ASSET_TYPES.NATIVE
|
||||||
? subtractHexWEIsFromRenderableEth(balance, customGasTotal)
|
? subtractHexWEIsFromRenderableEth(balance, customGasTotal)
|
||||||
: sumHexWEIsToRenderableEth([value, '0x0']);
|
: sumHexWEIsToRenderableEth([value, '0x0']);
|
||||||
|
|
||||||
@ -194,9 +192,7 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
txId: transaction.id,
|
txId: transaction.id,
|
||||||
insufficientBalance,
|
insufficientBalance,
|
||||||
isMainnet,
|
isMainnet,
|
||||||
sendToken,
|
|
||||||
balance,
|
balance,
|
||||||
tokenBalance: getTokenBalance(state),
|
|
||||||
conversionRate,
|
conversionRate,
|
||||||
value,
|
value,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
@ -213,12 +209,13 @@ const mapDispatchToProps = (dispatch) => {
|
|||||||
dispatch(hideModal());
|
dispatch(hideModal());
|
||||||
},
|
},
|
||||||
hideModal: () => dispatch(hideModal()),
|
hideModal: () => dispatch(hideModal()),
|
||||||
|
useCustomGas: () => dispatch(useCustomGas()),
|
||||||
updateCustomGasPrice,
|
updateCustomGasPrice,
|
||||||
updateCustomGasLimit: (newLimit) =>
|
updateCustomGasLimit: (newLimit) =>
|
||||||
dispatch(setCustomGasLimit(addHexPrefix(newLimit))),
|
dispatch(setCustomGasLimit(addHexPrefix(newLimit))),
|
||||||
setGasData: (newLimit, newPrice) => {
|
setGasData: (newLimit, newPrice) => {
|
||||||
dispatch(setGasLimit(newLimit));
|
dispatch(updateGasLimit(newLimit));
|
||||||
dispatch(setGasPrice(newPrice));
|
dispatch(updateGasPrice(newPrice));
|
||||||
},
|
},
|
||||||
updateConfirmTxGasAndCalculate: (gasLimit, gasPrice, updatedTx) => {
|
updateConfirmTxGasAndCalculate: (gasLimit, gasPrice, updatedTx) => {
|
||||||
updateCustomGasPrice(gasPrice);
|
updateCustomGasPrice(gasPrice);
|
||||||
@ -231,14 +228,8 @@ const mapDispatchToProps = (dispatch) => {
|
|||||||
createSpeedUpTransaction: (txId, gasPrice, gasLimit) => {
|
createSpeedUpTransaction: (txId, gasPrice, gasLimit) => {
|
||||||
return dispatch(createSpeedUpTransaction(txId, gasPrice, gasLimit));
|
return dispatch(createSpeedUpTransaction(txId, gasPrice, gasLimit));
|
||||||
},
|
},
|
||||||
hideGasButtonGroup: () => dispatch(hideGasButtonGroup()),
|
|
||||||
hideSidebar: () => dispatch(hideSidebar()),
|
hideSidebar: () => dispatch(hideSidebar()),
|
||||||
fetchBasicGasEstimates: () => dispatch(fetchBasicGasEstimates()),
|
fetchBasicGasEstimates: () => dispatch(fetchBasicGasEstimates()),
|
||||||
setGasTotal: (total) => dispatch(setGasTotal(total)),
|
|
||||||
setAmountToMax: (maxAmountDataObject) => {
|
|
||||||
dispatch(updateSendErrors({ amount: null }));
|
|
||||||
dispatch(updateSendAmount(calcMaxAmount(maxAmountDataObject)));
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -251,17 +242,12 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
|||||||
isSpeedUp,
|
isSpeedUp,
|
||||||
isRetry,
|
isRetry,
|
||||||
insufficientBalance,
|
insufficientBalance,
|
||||||
maxModeOn,
|
|
||||||
customGasPrice,
|
customGasPrice,
|
||||||
customGasTotal,
|
|
||||||
balance,
|
|
||||||
sendToken,
|
|
||||||
tokenBalance,
|
|
||||||
customGasLimit,
|
customGasLimit,
|
||||||
transaction,
|
transaction,
|
||||||
} = stateProps;
|
} = stateProps;
|
||||||
const {
|
const {
|
||||||
hideGasButtonGroup: dispatchHideGasButtonGroup,
|
useCustomGas: dispatchUseCustomGas,
|
||||||
setGasData: dispatchSetGasData,
|
setGasData: dispatchSetGasData,
|
||||||
updateConfirmTxGasAndCalculate: dispatchUpdateConfirmTxGasAndCalculate,
|
updateConfirmTxGasAndCalculate: dispatchUpdateConfirmTxGasAndCalculate,
|
||||||
createSpeedUpTransaction: dispatchCreateSpeedUpTransaction,
|
createSpeedUpTransaction: dispatchCreateSpeedUpTransaction,
|
||||||
@ -269,7 +255,6 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
|||||||
hideSidebar: dispatchHideSidebar,
|
hideSidebar: dispatchHideSidebar,
|
||||||
cancelAndClose: dispatchCancelAndClose,
|
cancelAndClose: dispatchCancelAndClose,
|
||||||
hideModal: dispatchHideModal,
|
hideModal: dispatchHideModal,
|
||||||
setAmountToMax: dispatchSetAmountToMax,
|
|
||||||
...otherDispatchProps
|
...otherDispatchProps
|
||||||
} = dispatchProps;
|
} = dispatchProps;
|
||||||
|
|
||||||
@ -305,17 +290,9 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
|||||||
dispatchCancelAndClose();
|
dispatchCancelAndClose();
|
||||||
} else {
|
} else {
|
||||||
dispatchSetGasData(gasLimit, gasPrice);
|
dispatchSetGasData(gasLimit, gasPrice);
|
||||||
dispatchHideGasButtonGroup();
|
dispatchUseCustomGas();
|
||||||
dispatchCancelAndClose();
|
dispatchCancelAndClose();
|
||||||
}
|
}
|
||||||
if (maxModeOn) {
|
|
||||||
dispatchSetAmountToMax({
|
|
||||||
balance,
|
|
||||||
gasTotal: customGasTotal,
|
|
||||||
sendToken,
|
|
||||||
tokenBalance,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
gasPriceButtonGroupProps: {
|
gasPriceButtonGroupProps: {
|
||||||
...gasPriceButtonGroupProps,
|
...gasPriceButtonGroupProps,
|
||||||
|
@ -17,7 +17,7 @@ import {
|
|||||||
} from '../../../hooks/useMetricEvent';
|
} from '../../../hooks/useMetricEvent';
|
||||||
import { useTokenTracker } from '../../../hooks/useTokenTracker';
|
import { useTokenTracker } from '../../../hooks/useTokenTracker';
|
||||||
import { useTokenFiatAmount } from '../../../hooks/useTokenFiatAmount';
|
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 { setSwapsFromToken } from '../../../ducks/swaps/swaps';
|
||||||
import {
|
import {
|
||||||
getAssetImages,
|
getAssetImages,
|
||||||
@ -85,8 +85,14 @@ const TokenOverview = ({ className, token }) => {
|
|||||||
className="token-overview__button"
|
className="token-overview__button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
sendTokenEvent();
|
sendTokenEvent();
|
||||||
dispatch(updateSendToken(token));
|
dispatch(
|
||||||
|
updateSendAsset({
|
||||||
|
type: ASSET_TYPES.TOKEN,
|
||||||
|
details: token,
|
||||||
|
}),
|
||||||
|
).then(() => {
|
||||||
history.push(SEND_ROUTE);
|
history.push(SEND_ROUTE);
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
Icon={SendIcon}
|
Icon={SendIcon}
|
||||||
label={t('send')}
|
label={t('send')}
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classnames from 'classnames';
|
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
|
* Component that attaches a suffix or unit of measurement trailing user input, ex. 'ETH'. Also
|
||||||
|
@ -15,10 +15,11 @@ import {
|
|||||||
getNumberOfAccounts,
|
getNumberOfAccounts,
|
||||||
getNumberOfTokens,
|
getNumberOfTokens,
|
||||||
} from '../selectors/selectors';
|
} from '../selectors/selectors';
|
||||||
import { getSendToken } from '../selectors/send';
|
import { getSendAsset, ASSET_TYPES } from '../ducks/send';
|
||||||
import { txDataSelector } from '../selectors/confirm-transaction';
|
import { txDataSelector } from '../selectors/confirm-transaction';
|
||||||
import { getEnvironmentType } from '../../app/scripts/lib/util';
|
import { getEnvironmentType } from '../../app/scripts/lib/util';
|
||||||
import { trackMetaMetricsEvent } from '../store/actions';
|
import { trackMetaMetricsEvent } from '../store/actions';
|
||||||
|
import { getNativeCurrency } from '../ducks/metamask/metamask';
|
||||||
|
|
||||||
export const MetaMetricsContext = createContext(() => {
|
export const MetaMetricsContext = createContext(() => {
|
||||||
captureException(
|
captureException(
|
||||||
@ -31,7 +32,8 @@ export const MetaMetricsContext = createContext(() => {
|
|||||||
export function MetaMetricsProvider({ children }) {
|
export function MetaMetricsProvider({ children }) {
|
||||||
const txData = useSelector(txDataSelector) || {};
|
const txData = useSelector(txDataSelector) || {};
|
||||||
const environmentType = getEnvironmentType();
|
const environmentType = getEnvironmentType();
|
||||||
const activeCurrency = useSelector(getSendToken)?.symbol;
|
const activeAsset = useSelector(getSendAsset);
|
||||||
|
const nativeAssetSymbol = useSelector(getNativeCurrency);
|
||||||
const accountType = useSelector(getAccountType);
|
const accountType = useSelector(getAccountType);
|
||||||
const confirmTransactionOrigin = txData.origin;
|
const confirmTransactionOrigin = txData.origin;
|
||||||
const numberOfTokens = useSelector(getNumberOfTokens);
|
const numberOfTokens = useSelector(getNumberOfTokens);
|
||||||
@ -72,7 +74,10 @@ export function MetaMetricsProvider({ children }) {
|
|||||||
action: eventOpts.action,
|
action: eventOpts.action,
|
||||||
number_of_tokens: numberOfTokens,
|
number_of_tokens: numberOfTokens,
|
||||||
number_of_accounts: numberOfAccounts,
|
number_of_accounts: numberOfAccounts,
|
||||||
active_currency: activeCurrency,
|
active_currency:
|
||||||
|
activeAsset.type === ASSET_TYPES.NATIVE
|
||||||
|
? nativeAssetSymbol
|
||||||
|
: activeAsset?.details?.symbol,
|
||||||
account_type: accountType,
|
account_type: accountType,
|
||||||
is_new_visit: config.is_new_visit,
|
is_new_visit: config.is_new_visit,
|
||||||
// the properties coming from this key will not match our standards for
|
// the properties coming from this key will not match our standards for
|
||||||
@ -102,7 +107,8 @@ export function MetaMetricsProvider({ children }) {
|
|||||||
accountType,
|
accountType,
|
||||||
currentPath,
|
currentPath,
|
||||||
confirmTransactionOrigin,
|
confirmTransactionOrigin,
|
||||||
activeCurrency,
|
activeAsset,
|
||||||
|
nativeAssetSymbol,
|
||||||
numberOfTokens,
|
numberOfTokens,
|
||||||
numberOfAccounts,
|
numberOfAccounts,
|
||||||
environmentType,
|
environmentType,
|
||||||
|
197
ui/ducks/ens.js
Normal file
197
ui/ducks/ens.js
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
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 = ENS_NOT_SUPPORTED_ON_NETWORK;
|
||||||
|
state.warning = null;
|
||||||
|
state.resolution = null;
|
||||||
|
state.network = null;
|
||||||
|
},
|
||||||
|
resetResolution: (state) => {
|
||||||
|
state.resolution = null;
|
||||||
|
state.warning = null;
|
||||||
|
state.error =
|
||||||
|
state.stage === 'NO_NETWORK_SUPPORT'
|
||||||
|
? ENS_NOT_SUPPORTED_ON_NETWORK
|
||||||
|
: 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,
|
||||||
|
resetResolution,
|
||||||
|
} = actions;
|
||||||
|
export { resetResolution };
|
||||||
|
|
||||||
|
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(resetResolution());
|
||||||
|
} 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,
|
fetchBasicGasEstimates,
|
||||||
} from './gas.duck';
|
} 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', () => ({
|
jest.mock('../../helpers/utils/storage-helpers.js', () => ({
|
||||||
getStorageItem: jest.fn(),
|
getStorageItem: jest.fn(),
|
||||||
setStorageItem: jest.fn(),
|
setStorageItem: jest.fn(),
|
||||||
@ -61,13 +69,6 @@ describe('Gas Duck', () => {
|
|||||||
type: 'mainnet',
|
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()', () => {
|
describe('GasReducer()', () => {
|
||||||
it('should initialize state', () => {
|
it('should initialize state', () => {
|
||||||
expect(GasReducer(undefined, {})).toStrictEqual(initState);
|
expect(GasReducer(undefined, {})).toStrictEqual(initState);
|
||||||
|
@ -10,6 +10,14 @@ import {
|
|||||||
} from '../../helpers/utils/conversions.util';
|
} from '../../helpers/utils/conversions.util';
|
||||||
import { getIsMainnet, getCurrentChainId } from '../../selectors';
|
import { getIsMainnet, getCurrentChainId } from '../../selectors';
|
||||||
import fetchWithCache from '../../helpers/utils/fetch-with-cache';
|
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 = {
|
export const BASIC_ESTIMATE_STATES = {
|
||||||
LOADING: 'LOADING',
|
LOADING: 'LOADING',
|
||||||
@ -22,14 +30,6 @@ export const GAS_SOURCE = {
|
|||||||
ETHGASPRICE: 'eth_gasprice',
|
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 = {
|
const initState = {
|
||||||
customData: {
|
customData: {
|
||||||
price: null,
|
price: null,
|
||||||
|
@ -2,7 +2,8 @@ import { combineReducers } from 'redux';
|
|||||||
import { ALERT_TYPES } from '../../shared/constants/alerts';
|
import { ALERT_TYPES } from '../../shared/constants/alerts';
|
||||||
import metamaskReducer from './metamask/metamask';
|
import metamaskReducer from './metamask/metamask';
|
||||||
import localeMessagesReducer from './locale/locale';
|
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 appStateReducer from './app/app';
|
||||||
import confirmTransactionReducer from './confirm-transaction/confirm-transaction.duck';
|
import confirmTransactionReducer from './confirm-transaction/confirm-transaction.duck';
|
||||||
import gasReducer from './gas/gas.duck';
|
import gasReducer from './gas/gas.duck';
|
||||||
@ -16,6 +17,7 @@ export default combineReducers({
|
|||||||
activeTab: (s) => (s === undefined ? null : s),
|
activeTab: (s) => (s === undefined ? null : s),
|
||||||
metamask: metamaskReducer,
|
metamask: metamaskReducer,
|
||||||
appState: appStateReducer,
|
appState: appStateReducer,
|
||||||
|
ENS: ensReducer,
|
||||||
history: historyReducer,
|
history: historyReducer,
|
||||||
send: sendReducer,
|
send: sendReducer,
|
||||||
confirmTransaction: confirmTransactionReducer,
|
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,
|
|
||||||
};
|
|
||||||
}
|
|
1472
ui/ducks/send/send.js
Normal file
1472
ui/ducks/send/send.js
Normal file
File diff suppressed because it is too large
Load Diff
1808
ui/ducks/send/send.test.js
Normal file
1808
ui/ducks/send/send.test.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,7 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { compose } from 'redux';
|
import { compose } from 'redux';
|
||||||
import { withRouter } from 'react-router-dom';
|
import { withRouter } from 'react-router-dom';
|
||||||
import { updateSend } from '../../ducks/send/send.duck';
|
import { ASSET_TYPES, editTransaction } from '../../ducks/send';
|
||||||
import { clearConfirmTransaction } from '../../ducks/confirm-transaction/confirm-transaction.duck';
|
import { clearConfirmTransaction } from '../../ducks/confirm-transaction/confirm-transaction.duck';
|
||||||
import ConfirmSendEther from './confirm-send-ether.component';
|
import ConfirmSendEther from './confirm-send-ether.component';
|
||||||
|
|
||||||
@ -18,22 +18,8 @@ const mapStateToProps = (state) => {
|
|||||||
const mapDispatchToProps = (dispatch) => {
|
const mapDispatchToProps = (dispatch) => {
|
||||||
return {
|
return {
|
||||||
editTransaction: (txData) => {
|
editTransaction: (txData) => {
|
||||||
const { id, txParams } = txData;
|
const { id } = txData;
|
||||||
const { from, gas: gasLimit, gasPrice, to, value: amount } = txParams;
|
dispatch(editTransaction(ASSET_TYPES.NATIVE, id.toString()));
|
||||||
|
|
||||||
dispatch(
|
|
||||||
updateSend({
|
|
||||||
from,
|
|
||||||
gasLimit,
|
|
||||||
gasPrice,
|
|
||||||
gasTotal: null,
|
|
||||||
to,
|
|
||||||
amount,
|
|
||||||
errors: { to: null, amount: null },
|
|
||||||
editingTransactionId: id?.toString(),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
dispatch(clearConfirmTransaction());
|
dispatch(clearConfirmTransaction());
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -3,13 +3,8 @@ import { compose } from 'redux';
|
|||||||
import { withRouter } from 'react-router-dom';
|
import { withRouter } from 'react-router-dom';
|
||||||
import { clearConfirmTransaction } from '../../ducks/confirm-transaction/confirm-transaction.duck';
|
import { clearConfirmTransaction } from '../../ducks/confirm-transaction/confirm-transaction.duck';
|
||||||
import { showSendTokenPage } from '../../store/actions';
|
import { showSendTokenPage } from '../../store/actions';
|
||||||
import { conversionUtil } from '../../helpers/utils/conversion-util';
|
import { ASSET_TYPES, editTransaction } from '../../ducks/send';
|
||||||
import {
|
|
||||||
getTokenValueParam,
|
|
||||||
getTokenAddressParam,
|
|
||||||
} from '../../helpers/utils/token-util';
|
|
||||||
import { sendTokenTokenAmountAndToAddressSelector } from '../../selectors';
|
import { sendTokenTokenAmountAndToAddressSelector } from '../../selectors';
|
||||||
import { updateSend } from '../../ducks/send/send.duck';
|
|
||||||
import ConfirmSendToken from './confirm-send-token.component';
|
import ConfirmSendToken from './confirm-send-token.component';
|
||||||
|
|
||||||
const mapStateToProps = (state) => {
|
const mapStateToProps = (state) => {
|
||||||
@ -22,35 +17,15 @@ const mapStateToProps = (state) => {
|
|||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => {
|
const mapDispatchToProps = (dispatch) => {
|
||||||
return {
|
return {
|
||||||
editTransaction: ({ txData, tokenData, tokenProps }) => {
|
editTransaction: ({ txData, tokenData, tokenProps: assetDetails }) => {
|
||||||
const {
|
const { id } = txData;
|
||||||
id,
|
|
||||||
txParams: { from, to: tokenAddress, gas: gasLimit, gasPrice } = {},
|
|
||||||
} = txData;
|
|
||||||
|
|
||||||
const to = getTokenValueParam(tokenData);
|
|
||||||
const tokenAmountInDec = getTokenAddressParam(tokenData);
|
|
||||||
|
|
||||||
const tokenAmountInHex = conversionUtil(tokenAmountInDec, {
|
|
||||||
fromNumericBase: 'dec',
|
|
||||||
toNumericBase: 'hex',
|
|
||||||
});
|
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
updateSend({
|
editTransaction(
|
||||||
from,
|
ASSET_TYPES.TOKEN,
|
||||||
gasLimit,
|
id.toString(),
|
||||||
gasPrice,
|
tokenData,
|
||||||
gasTotal: null,
|
assetDetails,
|
||||||
to,
|
),
|
||||||
amount: tokenAmountInHex,
|
|
||||||
errors: { to: null, amount: null },
|
|
||||||
editingTransactionId: id?.toString(),
|
|
||||||
token: {
|
|
||||||
...tokenProps,
|
|
||||||
address: tokenAddress,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
dispatch(clearConfirmTransaction());
|
dispatch(clearConfirmTransaction());
|
||||||
dispatch(showSendTokenPage());
|
dispatch(showSendTokenPage());
|
||||||
|
@ -12,6 +12,7 @@ import Loading from '../../components/ui/loading-screen';
|
|||||||
import { getMostRecentOverviewPage } from '../../ducks/history/history';
|
import { getMostRecentOverviewPage } from '../../ducks/history/history';
|
||||||
import { MESSAGE_TYPE } from '../../../shared/constants/app';
|
import { MESSAGE_TYPE } from '../../../shared/constants/app';
|
||||||
import { TRANSACTION_STATUSES } from '../../../shared/constants/transaction';
|
import { TRANSACTION_STATUSES } from '../../../shared/constants/transaction';
|
||||||
|
import { getSendTo } from '../../ducks/send';
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state) {
|
||||||
const { metamask, appState } = state;
|
const { metamask, appState } = state;
|
||||||
@ -38,7 +39,7 @@ function mapStateToProps(state) {
|
|||||||
unapprovedMsgCount,
|
unapprovedMsgCount,
|
||||||
unapprovedPersonalMsgCount,
|
unapprovedPersonalMsgCount,
|
||||||
unapprovedTypedMessagesCount,
|
unapprovedTypedMessagesCount,
|
||||||
send: state.send,
|
sendTo: getSendTo(state),
|
||||||
currentNetworkTxList: state.metamask.currentNetworkTxList,
|
currentNetworkTxList: state.metamask.currentNetworkTxList,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -68,9 +69,7 @@ class ConfirmTxScreen extends Component {
|
|||||||
history: PropTypes.object,
|
history: PropTypes.object,
|
||||||
identities: PropTypes.object,
|
identities: PropTypes.object,
|
||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
send: PropTypes.shape({
|
sendTo: PropTypes.string,
|
||||||
to: PropTypes.string,
|
|
||||||
}).isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
getUnapprovedMessagesTotal() {
|
getUnapprovedMessagesTotal() {
|
||||||
@ -182,13 +181,13 @@ class ConfirmTxScreen extends Component {
|
|||||||
mostRecentOverviewPage,
|
mostRecentOverviewPage,
|
||||||
network,
|
network,
|
||||||
chainId,
|
chainId,
|
||||||
send,
|
sendTo,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const unconfTxList = txHelper(unapprovedTxs, {}, {}, {}, network, chainId);
|
const unconfTxList = txHelper(unapprovedTxs, {}, {}, {}, network, chainId);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
unconfTxList.length === 0 &&
|
unconfTxList.length === 0 &&
|
||||||
!send.to &&
|
!sendTo &&
|
||||||
this.getUnapprovedMessagesTotal() === 0
|
this.getUnapprovedMessagesTotal() === 0
|
||||||
) {
|
) {
|
||||||
history.push(mostRecentOverviewPage);
|
history.push(mostRecentOverviewPage);
|
||||||
@ -201,7 +200,7 @@ class ConfirmTxScreen extends Component {
|
|||||||
network,
|
network,
|
||||||
chainId,
|
chainId,
|
||||||
currentNetworkTxList,
|
currentNetworkTxList,
|
||||||
send,
|
sendTo,
|
||||||
history,
|
history,
|
||||||
match: { params: { id: transactionId } = {} },
|
match: { params: { id: transactionId } = {} },
|
||||||
mostRecentOverviewPage,
|
mostRecentOverviewPage,
|
||||||
@ -241,7 +240,7 @@ class ConfirmTxScreen extends Component {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
unconfTxList.length === 0 &&
|
unconfTxList.length === 0 &&
|
||||||
!send.to &&
|
!sendTo &&
|
||||||
this.getUnapprovedMessagesTotal() === 0
|
this.getUnapprovedMessagesTotal() === 0
|
||||||
) {
|
) {
|
||||||
this.props.history.push(mostRecentOverviewPage);
|
this.props.history.push(mostRecentOverviewPage);
|
||||||
|
@ -35,7 +35,7 @@ export default class ConfirmTransaction extends Component {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
history: PropTypes.object.isRequired,
|
history: PropTypes.object.isRequired,
|
||||||
totalUnapprovedCount: PropTypes.number.isRequired,
|
totalUnapprovedCount: PropTypes.number.isRequired,
|
||||||
send: PropTypes.object,
|
sendTo: PropTypes.string,
|
||||||
setTransactionToConfirm: PropTypes.func,
|
setTransactionToConfirm: PropTypes.func,
|
||||||
clearConfirmTransaction: PropTypes.func,
|
clearConfirmTransaction: PropTypes.func,
|
||||||
fetchBasicGasEstimates: PropTypes.func,
|
fetchBasicGasEstimates: PropTypes.func,
|
||||||
@ -52,7 +52,7 @@ export default class ConfirmTransaction extends Component {
|
|||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const {
|
const {
|
||||||
totalUnapprovedCount = 0,
|
totalUnapprovedCount = 0,
|
||||||
send = {},
|
sendTo,
|
||||||
history,
|
history,
|
||||||
mostRecentOverviewPage,
|
mostRecentOverviewPage,
|
||||||
transaction: { txParams: { data, to } = {} } = {},
|
transaction: { txParams: { data, to } = {} } = {},
|
||||||
@ -64,7 +64,7 @@ export default class ConfirmTransaction extends Component {
|
|||||||
isTokenMethodAction,
|
isTokenMethodAction,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
if (!totalUnapprovedCount && !send.to) {
|
if (!totalUnapprovedCount && !sendTo) {
|
||||||
history.replace(mostRecentOverviewPage);
|
history.replace(mostRecentOverviewPage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -15,17 +15,18 @@ import {
|
|||||||
} from '../../store/actions';
|
} from '../../store/actions';
|
||||||
import { unconfirmedTransactionsListSelector } from '../../selectors';
|
import { unconfirmedTransactionsListSelector } from '../../selectors';
|
||||||
import { getMostRecentOverviewPage } from '../../ducks/history/history';
|
import { getMostRecentOverviewPage } from '../../ducks/history/history';
|
||||||
|
import { getSendTo } from '../../ducks/send';
|
||||||
import ConfirmTransaction from './confirm-transaction.component';
|
import ConfirmTransaction from './confirm-transaction.component';
|
||||||
|
|
||||||
const mapStateToProps = (state, ownProps) => {
|
const mapStateToProps = (state, ownProps) => {
|
||||||
const {
|
const {
|
||||||
metamask: { unapprovedTxs },
|
metamask: { unapprovedTxs },
|
||||||
send,
|
|
||||||
} = state;
|
} = state;
|
||||||
const {
|
const {
|
||||||
match: { params = {} },
|
match: { params = {} },
|
||||||
} = ownProps;
|
} = ownProps;
|
||||||
const { id } = params;
|
const { id } = params;
|
||||||
|
const sendTo = getSendTo(state);
|
||||||
|
|
||||||
const unconfirmedTransactions = unconfirmedTransactionsListSelector(state);
|
const unconfirmedTransactions = unconfirmedTransactionsListSelector(state);
|
||||||
const totalUnconfirmed = unconfirmedTransactions.length;
|
const totalUnconfirmed = unconfirmedTransactions.length;
|
||||||
@ -36,7 +37,7 @@ const mapStateToProps = (state, ownProps) => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
totalUnapprovedCount: totalUnconfirmed,
|
totalUnapprovedCount: totalUnconfirmed,
|
||||||
send,
|
sendTo,
|
||||||
unapprovedTxs,
|
unapprovedTxs,
|
||||||
id,
|
id,
|
||||||
mostRecentOverviewPage: getMostRecentOverviewPage(state),
|
mostRecentOverviewPage: getMostRecentOverviewPage(state),
|
||||||
|
@ -1 +1 @@
|
|||||||
export { default } from './send.container';
|
export { default } from './send';
|
||||||
|
@ -8,26 +8,28 @@ import RecipientGroup from '../../../../components/app/contact-list/recipient-gr
|
|||||||
import { ellipsify } from '../../send.utils';
|
import { ellipsify } from '../../send.utils';
|
||||||
import Button from '../../../../components/ui/button';
|
import Button from '../../../../components/ui/button';
|
||||||
import Confusable from '../../../../components/ui/confusable';
|
import Confusable from '../../../../components/ui/confusable';
|
||||||
import {
|
|
||||||
isBurnAddress,
|
|
||||||
isValidHexAddress,
|
|
||||||
} from '../../../../../shared/modules/hexstring-utils';
|
|
||||||
|
|
||||||
export default class AddRecipient extends Component {
|
export default class AddRecipient extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
query: PropTypes.string,
|
userInput: PropTypes.string,
|
||||||
ownedAccounts: PropTypes.array,
|
ownedAccounts: PropTypes.array,
|
||||||
addressBook: PropTypes.array,
|
addressBook: PropTypes.array,
|
||||||
updateGas: PropTypes.func,
|
updateRecipient: PropTypes.func,
|
||||||
updateSendTo: PropTypes.func,
|
|
||||||
ensResolution: PropTypes.string,
|
ensResolution: PropTypes.string,
|
||||||
toError: PropTypes.string,
|
ensError: PropTypes.string,
|
||||||
toWarning: PropTypes.string,
|
ensWarning: PropTypes.string,
|
||||||
ensResolutionError: PropTypes.string,
|
|
||||||
addressBookEntryName: PropTypes.string,
|
addressBookEntryName: PropTypes.string,
|
||||||
contacts: PropTypes.array,
|
contacts: PropTypes.array,
|
||||||
nonContacts: PropTypes.array,
|
nonContacts: PropTypes.array,
|
||||||
setInternalSearch: PropTypes.func,
|
useMyAccountsForRecipientSearch: PropTypes.func,
|
||||||
|
useContactListForRecipientSearch: PropTypes.func,
|
||||||
|
isUsingMyAccountsForRecipientSearch: PropTypes.bool,
|
||||||
|
recipient: PropTypes.shape({
|
||||||
|
address: PropTypes.string,
|
||||||
|
nickname: PropTypes.nickname,
|
||||||
|
error: PropTypes.string,
|
||||||
|
warning: PropTypes.string,
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@ -61,60 +63,58 @@ export default class AddRecipient extends Component {
|
|||||||
metricsEvent: PropTypes.func,
|
metricsEvent: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
selectRecipient = (address, nickname = '') => {
|
||||||
isShowingTransfer: false,
|
this.props.updateRecipient({ address, nickname });
|
||||||
};
|
|
||||||
|
|
||||||
selectRecipient = (to, nickname = '') => {
|
|
||||||
const { updateSendTo, updateGas } = this.props;
|
|
||||||
|
|
||||||
updateSendTo(to, nickname);
|
|
||||||
updateGas({ to });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
searchForContacts = () => {
|
searchForContacts = () => {
|
||||||
const { query, contacts } = this.props;
|
const { userInput, contacts } = this.props;
|
||||||
|
|
||||||
let _contacts = contacts;
|
let _contacts = contacts;
|
||||||
|
|
||||||
if (query) {
|
if (userInput) {
|
||||||
this.contactFuse.setCollection(contacts);
|
this.contactFuse.setCollection(contacts);
|
||||||
_contacts = this.contactFuse.search(query);
|
_contacts = this.contactFuse.search(userInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
return _contacts;
|
return _contacts;
|
||||||
};
|
};
|
||||||
|
|
||||||
searchForRecents = () => {
|
searchForRecents = () => {
|
||||||
const { query, nonContacts } = this.props;
|
const { userInput, nonContacts } = this.props;
|
||||||
|
|
||||||
let _nonContacts = nonContacts;
|
let _nonContacts = nonContacts;
|
||||||
|
|
||||||
if (query) {
|
if (userInput) {
|
||||||
this.recentFuse.setCollection(nonContacts);
|
this.recentFuse.setCollection(nonContacts);
|
||||||
_nonContacts = this.recentFuse.search(query);
|
_nonContacts = this.recentFuse.search(userInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
return _nonContacts;
|
return _nonContacts;
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { ensResolution, query, addressBookEntryName } = this.props;
|
const {
|
||||||
const { isShowingTransfer } = this.state;
|
ensResolution,
|
||||||
|
recipient,
|
||||||
|
userInput,
|
||||||
|
addressBookEntryName,
|
||||||
|
isUsingMyAccountsForRecipientSearch,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
let content;
|
let content;
|
||||||
|
|
||||||
if (
|
if (recipient.address) {
|
||||||
!isBurnAddress(query) &&
|
content = this.renderExplicitAddress(
|
||||||
isValidHexAddress(query, { mixedCaseUseChecksum: true })
|
recipient.address,
|
||||||
) {
|
recipient.nickname,
|
||||||
content = this.renderExplicitAddress(query);
|
);
|
||||||
} else if (ensResolution) {
|
} else if (ensResolution) {
|
||||||
content = this.renderExplicitAddress(
|
content = this.renderExplicitAddress(
|
||||||
ensResolution,
|
ensResolution,
|
||||||
addressBookEntryName || query,
|
addressBookEntryName || userInput,
|
||||||
);
|
);
|
||||||
} else if (isShowingTransfer) {
|
} else if (isUsingMyAccountsForRecipientSearch) {
|
||||||
content = this.renderTransfer();
|
content = this.renderTransfer();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,15 +150,18 @@ export default class AddRecipient extends Component {
|
|||||||
|
|
||||||
renderTransfer() {
|
renderTransfer() {
|
||||||
let { ownedAccounts } = this.props;
|
let { ownedAccounts } = this.props;
|
||||||
const { query, setInternalSearch } = this.props;
|
const {
|
||||||
|
userInput,
|
||||||
|
useContactListForRecipientSearch,
|
||||||
|
isUsingMyAccountsForRecipientSearch,
|
||||||
|
} = this.props;
|
||||||
const { t } = this.context;
|
const { t } = this.context;
|
||||||
const { isShowingTransfer } = this.state;
|
|
||||||
|
|
||||||
if (isShowingTransfer && query) {
|
if (isUsingMyAccountsForRecipientSearch && userInput) {
|
||||||
ownedAccounts = ownedAccounts.filter(
|
ownedAccounts = ownedAccounts.filter(
|
||||||
(item) =>
|
(item) =>
|
||||||
item.name.toLowerCase().indexOf(query.toLowerCase()) > -1 ||
|
item.name.toLowerCase().indexOf(userInput.toLowerCase()) > -1 ||
|
||||||
item.address.toLowerCase().indexOf(query.toLowerCase()) > -1,
|
item.address.toLowerCase().indexOf(userInput.toLowerCase()) > -1,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,10 +170,7 @@ export default class AddRecipient extends Component {
|
|||||||
<Button
|
<Button
|
||||||
type="link"
|
type="link"
|
||||||
className="send__select-recipient-wrapper__list__link"
|
className="send__select-recipient-wrapper__list__link"
|
||||||
onClick={() => {
|
onClick={useContactListForRecipientSearch}
|
||||||
setInternalSearch(false);
|
|
||||||
this.setState({ isShowingTransfer: false });
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<div className="send__select-recipient-wrapper__list__back-caret" />
|
<div className="send__select-recipient-wrapper__list__back-caret" />
|
||||||
{t('backToAll')}
|
{t('backToAll')}
|
||||||
@ -187,10 +187,10 @@ export default class AddRecipient extends Component {
|
|||||||
renderMain() {
|
renderMain() {
|
||||||
const { t } = this.context;
|
const { t } = this.context;
|
||||||
const {
|
const {
|
||||||
query,
|
userInput,
|
||||||
ownedAccounts = [],
|
ownedAccounts = [],
|
||||||
addressBook,
|
addressBook,
|
||||||
setInternalSearch,
|
useMyAccountsForRecipientSearch,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -201,14 +201,11 @@ export default class AddRecipient extends Component {
|
|||||||
searchForRecents={this.searchForRecents.bind(this)}
|
searchForRecents={this.searchForRecents.bind(this)}
|
||||||
selectRecipient={this.selectRecipient.bind(this)}
|
selectRecipient={this.selectRecipient.bind(this)}
|
||||||
>
|
>
|
||||||
{ownedAccounts && ownedAccounts.length > 1 && !query && (
|
{ownedAccounts && ownedAccounts.length > 1 && !userInput && (
|
||||||
<Button
|
<Button
|
||||||
type="link"
|
type="link"
|
||||||
className="send__select-recipient-wrapper__list__link"
|
className="send__select-recipient-wrapper__list__link"
|
||||||
onClick={() => {
|
onClick={useMyAccountsForRecipientSearch}
|
||||||
setInternalSearch(true);
|
|
||||||
this.setState({ isShowingTransfer: true });
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{t('transferBetweenAccounts')}
|
{t('transferBetweenAccounts')}
|
||||||
</Button>
|
</Button>
|
||||||
@ -219,30 +216,19 @@ export default class AddRecipient extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderDialogs() {
|
renderDialogs() {
|
||||||
const {
|
const { ensError, recipient, ensWarning } = this.props;
|
||||||
toError,
|
|
||||||
toWarning,
|
|
||||||
ensResolutionError,
|
|
||||||
ensResolution,
|
|
||||||
} = this.props;
|
|
||||||
const { t } = this.context;
|
const { t } = this.context;
|
||||||
|
|
||||||
if (ensResolutionError) {
|
if (ensError || (recipient.error && recipient.error !== 'required')) {
|
||||||
return (
|
return (
|
||||||
<Dialog type="error" className="send__error-dialog">
|
<Dialog type="error" className="send__error-dialog">
|
||||||
{ensResolutionError}
|
{t(ensError ?? recipient.error)}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
} else if (toError && toError !== 'required' && !ensResolution) {
|
} else if (ensWarning || recipient.warning) {
|
||||||
return (
|
|
||||||
<Dialog type="error" className="send__error-dialog">
|
|
||||||
{t(toError)}
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
} else if (toWarning) {
|
|
||||||
return (
|
return (
|
||||||
<Dialog type="warning" className="send__error-dialog">
|
<Dialog type="warning" className="send__error-dialog">
|
||||||
{t(toWarning)}
|
{t(ensWarning ?? recipient.warning)}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -5,30 +5,24 @@ import Dialog from '../../../../components/ui/dialog';
|
|||||||
import AddRecipient from './add-recipient.component';
|
import AddRecipient from './add-recipient.component';
|
||||||
|
|
||||||
const propsMethodSpies = {
|
const propsMethodSpies = {
|
||||||
closeToDropdown: sinon.spy(),
|
updateRecipient: sinon.spy(),
|
||||||
openToDropdown: sinon.spy(),
|
useMyAccountsForRecipientSearch: sinon.spy(),
|
||||||
updateGas: sinon.spy(),
|
useContactListForRecipientSearch: sinon.spy(),
|
||||||
updateSendTo: sinon.spy(),
|
|
||||||
updateSendToError: sinon.spy(),
|
|
||||||
updateSendToWarning: sinon.spy(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('AddRecipient Component', () => {
|
describe('AddRecipient Component', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
let instance;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
wrapper = shallow(
|
wrapper = shallow(
|
||||||
<AddRecipient
|
<AddRecipient
|
||||||
closeToDropdown={propsMethodSpies.closeToDropdown}
|
userInput=""
|
||||||
inError={false}
|
recipient={{
|
||||||
inWarning={false}
|
address: '',
|
||||||
network="mockNetwork"
|
nickname: '',
|
||||||
openToDropdown={propsMethodSpies.openToDropdown}
|
error: '',
|
||||||
to="mockTo"
|
warning: '',
|
||||||
toAccounts={['mockAccount']}
|
}}
|
||||||
toDropdownOpen={false}
|
|
||||||
updateGas={propsMethodSpies.updateGas}
|
|
||||||
updateSendTo={propsMethodSpies.updateSendTo}
|
updateSendTo={propsMethodSpies.updateSendTo}
|
||||||
updateSendToError={propsMethodSpies.updateSendToError}
|
updateSendToError={propsMethodSpies.updateSendToError}
|
||||||
updateSendToWarning={propsMethodSpies.updateSendToWarning}
|
updateSendToWarning={propsMethodSpies.updateSendToWarning}
|
||||||
@ -53,34 +47,12 @@ describe('AddRecipient Component', () => {
|
|||||||
/>,
|
/>,
|
||||||
{ context: { t: (str) => `${str}_t` } },
|
{ context: { t: (str) => `${str}_t` } },
|
||||||
);
|
);
|
||||||
instance = wrapper.instance();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
propsMethodSpies.closeToDropdown.resetHistory();
|
propsMethodSpies.updateRecipient.resetHistory();
|
||||||
propsMethodSpies.openToDropdown.resetHistory();
|
propsMethodSpies.useMyAccountsForRecipientSearch.resetHistory();
|
||||||
propsMethodSpies.updateSendTo.resetHistory();
|
propsMethodSpies.useContactListForRecipientSearch.resetHistory();
|
||||||
propsMethodSpies.updateSendToError.resetHistory();
|
|
||||||
propsMethodSpies.updateSendToWarning.resetHistory();
|
|
||||||
propsMethodSpies.updateGas.resetHistory();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('selectRecipient', () => {
|
|
||||||
it('should call updateSendTo', () => {
|
|
||||||
expect(propsMethodSpies.updateSendTo.callCount).toStrictEqual(0);
|
|
||||||
instance.selectRecipient('mockTo2', 'mockNickname');
|
|
||||||
expect(propsMethodSpies.updateSendTo.callCount).toStrictEqual(1);
|
|
||||||
expect(propsMethodSpies.updateSendTo.getCall(0).args).toStrictEqual([
|
|
||||||
'mockTo2',
|
|
||||||
'mockNickname',
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call updateGas if there is no to error', () => {
|
|
||||||
expect(propsMethodSpies.updateGas.callCount).toStrictEqual(0);
|
|
||||||
instance.selectRecipient(false);
|
|
||||||
expect(propsMethodSpies.updateGas.callCount).toStrictEqual(1);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('render', () => {
|
describe('render', () => {
|
||||||
@ -104,6 +76,7 @@ describe('AddRecipient Component', () => {
|
|||||||
|
|
||||||
it('should render transfer', () => {
|
it('should render transfer', () => {
|
||||||
wrapper.setProps({
|
wrapper.setProps({
|
||||||
|
isUsingMyAccountsForRecipientSearch: true,
|
||||||
ownedAccounts: [
|
ownedAccounts: [
|
||||||
{ address: '0x123', name: '123' },
|
{ address: '0x123', name: '123' },
|
||||||
{ address: '0x124', name: '124' },
|
{ address: '0x124', name: '124' },
|
||||||
@ -163,7 +136,7 @@ describe('AddRecipient Component', () => {
|
|||||||
it('should render error when query has no results', () => {
|
it('should render error when query has no results', () => {
|
||||||
wrapper.setProps({
|
wrapper.setProps({
|
||||||
addressBook: [],
|
addressBook: [],
|
||||||
toError: 'bad',
|
ensError: 'bad',
|
||||||
contacts: [],
|
contacts: [],
|
||||||
nonContacts: [],
|
nonContacts: [],
|
||||||
});
|
});
|
||||||
@ -178,8 +151,7 @@ describe('AddRecipient Component', () => {
|
|||||||
it('should render error when query has ens does not resolve', () => {
|
it('should render error when query has ens does not resolve', () => {
|
||||||
wrapper.setProps({
|
wrapper.setProps({
|
||||||
addressBook: [],
|
addressBook: [],
|
||||||
toError: 'bad',
|
ensError: 'very bad',
|
||||||
ensResolutionError: 'very bad',
|
|
||||||
contacts: [],
|
contacts: [],
|
||||||
nonContacts: [],
|
nonContacts: [],
|
||||||
});
|
});
|
||||||
@ -187,20 +159,20 @@ describe('AddRecipient Component', () => {
|
|||||||
const dialog = wrapper.find(Dialog);
|
const dialog = wrapper.find(Dialog);
|
||||||
|
|
||||||
expect(dialog.props().type).toStrictEqual('error');
|
expect(dialog.props().type).toStrictEqual('error');
|
||||||
expect(dialog.props().children).toStrictEqual('very bad');
|
expect(dialog.props().children).toStrictEqual('very bad_t');
|
||||||
expect(dialog).toHaveLength(1);
|
expect(dialog).toHaveLength(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not render error when ens resolved', () => {
|
it('should render error when ens resolved but ens error exists', () => {
|
||||||
wrapper.setProps({
|
wrapper.setProps({
|
||||||
addressBook: [],
|
addressBook: [],
|
||||||
toError: 'bad',
|
ensError: 'bad',
|
||||||
ensResolution: '0x128',
|
ensResolution: '0x128',
|
||||||
});
|
});
|
||||||
|
|
||||||
const dialog = wrapper.find(Dialog);
|
const dialog = wrapper.find(Dialog);
|
||||||
|
|
||||||
expect(dialog).toHaveLength(0);
|
expect(dialog).toHaveLength(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,19 +1,30 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
getSendEnsResolution,
|
|
||||||
getSendEnsResolutionError,
|
|
||||||
accountsWithSendEtherInfoSelector,
|
accountsWithSendEtherInfoSelector,
|
||||||
getAddressBook,
|
getAddressBook,
|
||||||
getAddressBookEntry,
|
getAddressBookEntry,
|
||||||
} from '../../../../selectors';
|
} from '../../../../selectors';
|
||||||
|
|
||||||
import { updateSendTo } from '../../../../ducks/send/send.duck';
|
import {
|
||||||
|
updateRecipient,
|
||||||
|
updateRecipientUserInput,
|
||||||
|
useMyAccountsForRecipientSearch,
|
||||||
|
useContactListForRecipientSearch,
|
||||||
|
getIsUsingMyAccountForRecipientSearch,
|
||||||
|
getRecipientUserInput,
|
||||||
|
getRecipient,
|
||||||
|
} from '../../../../ducks/send';
|
||||||
|
import {
|
||||||
|
getEnsResolution,
|
||||||
|
getEnsError,
|
||||||
|
getEnsWarning,
|
||||||
|
} from '../../../../ducks/ens';
|
||||||
import AddRecipient from './add-recipient.component';
|
import AddRecipient from './add-recipient.component';
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(AddRecipient);
|
export default connect(mapStateToProps, mapDispatchToProps)(AddRecipient);
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state) {
|
||||||
const ensResolution = getSendEnsResolution(state);
|
const ensResolution = getEnsResolution(state);
|
||||||
|
|
||||||
let addressBookEntryName = '';
|
let addressBookEntryName = '';
|
||||||
if (ensResolution) {
|
if (ensResolution) {
|
||||||
@ -32,14 +43,27 @@ function mapStateToProps(state) {
|
|||||||
addressBookEntryName,
|
addressBookEntryName,
|
||||||
contacts: addressBook.filter(({ name }) => Boolean(name)),
|
contacts: addressBook.filter(({ name }) => Boolean(name)),
|
||||||
ensResolution,
|
ensResolution,
|
||||||
ensResolutionError: getSendEnsResolutionError(state),
|
ensError: getEnsError(state),
|
||||||
|
ensWarning: getEnsWarning(state),
|
||||||
nonContacts: addressBook.filter(({ name }) => !name),
|
nonContacts: addressBook.filter(({ name }) => !name),
|
||||||
ownedAccounts,
|
ownedAccounts,
|
||||||
|
isUsingMyAccountsForRecipientSearch: getIsUsingMyAccountForRecipientSearch(
|
||||||
|
state,
|
||||||
|
),
|
||||||
|
userInput: getRecipientUserInput(state),
|
||||||
|
recipient: getRecipient(state),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
function mapDispatchToProps(dispatch) {
|
||||||
return {
|
return {
|
||||||
updateSendTo: (to, nickname) => dispatch(updateSendTo(to, nickname)),
|
updateRecipient: ({ address, nickname }) =>
|
||||||
|
dispatch(updateRecipient({ address, nickname })),
|
||||||
|
updateRecipientUserInput: (newInput) =>
|
||||||
|
dispatch(updateRecipientUserInput(newInput)),
|
||||||
|
useMyAccountsForRecipientSearch: () =>
|
||||||
|
dispatch(useMyAccountsForRecipientSearch()),
|
||||||
|
useContactListForRecipientSearch: () =>
|
||||||
|
dispatch(useContactListForRecipientSearch()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
import sinon from 'sinon';
|
|
||||||
import { updateSendTo } from '../../../../ducks/send/send.duck';
|
|
||||||
|
|
||||||
let mapStateToProps;
|
let mapStateToProps;
|
||||||
let mapDispatchToProps;
|
let mapDispatchToProps;
|
||||||
|
|
||||||
@ -13,8 +10,6 @@ jest.mock('react-redux', () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('../../../../selectors', () => ({
|
jest.mock('../../../../selectors', () => ({
|
||||||
getSendEnsResolution: (s) => `mockSendEnsResolution:${s}`,
|
|
||||||
getSendEnsResolutionError: (s) => `mockSendEnsResolutionError:${s}`,
|
|
||||||
getAddressBook: (s) => [{ name: `mockAddressBook:${s}` }],
|
getAddressBook: (s) => [{ name: `mockAddressBook:${s}` }],
|
||||||
getAddressBookEntry: (s) => `mockAddressBookEntry:${s}`,
|
getAddressBookEntry: (s) => `mockAddressBookEntry:${s}`,
|
||||||
accountsWithSendEtherInfoSelector: () => [
|
accountsWithSendEtherInfoSelector: () => [
|
||||||
@ -23,8 +18,26 @@ jest.mock('../../../../selectors', () => ({
|
|||||||
],
|
],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('../../../../ducks/send/send.duck.js', () => ({
|
jest.mock('../../../../ducks/ens', () => ({
|
||||||
updateSendTo: jest.fn(),
|
getEnsResolution: (s) => `mockSendEnsResolution:${s}`,
|
||||||
|
getEnsError: (s) => `mockSendEnsResolutionError:${s}`,
|
||||||
|
getEnsWarning: (s) => `mockSendEnsResolutionWarning:${s}`,
|
||||||
|
useMyAccountsForRecipientSearch: (s) =>
|
||||||
|
`useMyAccountsForRecipientSearch:${s}`,
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('../../../../ducks/send', () => ({
|
||||||
|
updateRecipient: ({ address, nickname }) =>
|
||||||
|
`{mockUpdateRecipient: {address: ${address}, nickname: ${nickname}}}`,
|
||||||
|
updateRecipientUserInput: (s) => `mockUpdateRecipientUserInput:${s}`,
|
||||||
|
useMyAccountsForRecipientSearch: (s) =>
|
||||||
|
`mockUseMyAccountsForRecipientSearch:${s}`,
|
||||||
|
useContactListForRecipientSearch: (s) =>
|
||||||
|
`mockUseContactListForRecipientSearch:${s}`,
|
||||||
|
getIsUsingMyAccountForRecipientSearch: (s) =>
|
||||||
|
`mockGetIsUsingMyAccountForRecipientSearch:${s}`,
|
||||||
|
getRecipientUserInput: (s) => `mockRecipientUserInput:${s}`,
|
||||||
|
getRecipient: (s) => `mockRecipient:${s}`,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
require('./add-recipient.container.js');
|
require('./add-recipient.container.js');
|
||||||
@ -34,29 +47,40 @@ describe('add-recipient container', () => {
|
|||||||
it('should map the correct properties to props', () => {
|
it('should map the correct properties to props', () => {
|
||||||
expect(mapStateToProps('mockState')).toStrictEqual({
|
expect(mapStateToProps('mockState')).toStrictEqual({
|
||||||
addressBook: [{ name: 'mockAddressBook:mockState' }],
|
addressBook: [{ name: 'mockAddressBook:mockState' }],
|
||||||
|
addressBookEntryName: undefined,
|
||||||
contacts: [{ name: 'mockAddressBook:mockState' }],
|
contacts: [{ name: 'mockAddressBook:mockState' }],
|
||||||
ensResolution: 'mockSendEnsResolution:mockState',
|
ensResolution: 'mockSendEnsResolution:mockState',
|
||||||
ensResolutionError: 'mockSendEnsResolutionError:mockState',
|
ensError: 'mockSendEnsResolutionError:mockState',
|
||||||
ownedAccounts: [
|
ensWarning: 'mockSendEnsResolutionWarning:mockState',
|
||||||
{ name: `account1:mockState` },
|
|
||||||
{ name: `account2:mockState` },
|
|
||||||
],
|
|
||||||
addressBookEntryName: undefined,
|
|
||||||
nonContacts: [],
|
nonContacts: [],
|
||||||
|
ownedAccounts: [
|
||||||
|
{ name: 'account1:mockState' },
|
||||||
|
{ name: 'account2:mockState' },
|
||||||
|
],
|
||||||
|
isUsingMyAccountsForRecipientSearch:
|
||||||
|
'mockGetIsUsingMyAccountForRecipientSearch:mockState',
|
||||||
|
userInput: 'mockRecipientUserInput:mockState',
|
||||||
|
recipient: 'mockRecipient:mockState',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('mapDispatchToProps()', () => {
|
describe('mapDispatchToProps()', () => {
|
||||||
describe('updateSendTo()', () => {
|
describe('updateRecipient()', () => {
|
||||||
const dispatchSpy = sinon.spy();
|
const dispatchSpy = jest.fn();
|
||||||
|
|
||||||
const mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy);
|
const mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy);
|
||||||
|
|
||||||
it('should dispatch an action', () => {
|
it('should dispatch an action', () => {
|
||||||
mapDispatchToPropsObject.updateSendTo('mockTo', 'mockNickname');
|
mapDispatchToPropsObject.updateRecipient({
|
||||||
expect(dispatchSpy.calledOnce).toStrictEqual(true);
|
address: 'mockAddress',
|
||||||
expect(updateSendTo).toHaveBeenCalled();
|
nickname: 'mockNickname',
|
||||||
expect(updateSendTo).toHaveBeenCalledWith('mockTo', 'mockNickname');
|
});
|
||||||
|
|
||||||
|
expect(dispatchSpy).toHaveBeenCalledTimes(1);
|
||||||
|
expect(dispatchSpy.mock.calls[0][0]).toStrictEqual(
|
||||||
|
'{mockUpdateRecipient: {address: mockAddress, nickname: mockNickname}}',
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,56 +0,0 @@
|
|||||||
import contractMap from '@metamask/contract-metadata';
|
|
||||||
import { isConfusing } from 'unicode-confusables';
|
|
||||||
import {
|
|
||||||
REQUIRED_ERROR,
|
|
||||||
INVALID_RECIPIENT_ADDRESS_ERROR,
|
|
||||||
KNOWN_RECIPIENT_ADDRESS_ERROR,
|
|
||||||
INVALID_RECIPIENT_ADDRESS_NOT_ETH_NETWORK_ERROR,
|
|
||||||
CONFUSING_ENS_ERROR,
|
|
||||||
CONTRACT_ADDRESS_ERROR,
|
|
||||||
} from '../../send.constants';
|
|
||||||
|
|
||||||
import {
|
|
||||||
checkExistingAddresses,
|
|
||||||
isValidDomainName,
|
|
||||||
isOriginContractAddress,
|
|
||||||
isDefaultMetaMaskChain,
|
|
||||||
} from '../../../../helpers/utils/util';
|
|
||||||
import {
|
|
||||||
isBurnAddress,
|
|
||||||
isValidHexAddress,
|
|
||||||
toChecksumHexAddress,
|
|
||||||
} from '../../../../../shared/modules/hexstring-utils';
|
|
||||||
|
|
||||||
export function getToErrorObject(to, sendTokenAddress, chainId) {
|
|
||||||
let toError = null;
|
|
||||||
if (!to) {
|
|
||||||
toError = REQUIRED_ERROR;
|
|
||||||
} else if (
|
|
||||||
isBurnAddress(to) ||
|
|
||||||
(!isValidHexAddress(to, { mixedCaseUseChecksum: true }) &&
|
|
||||||
!isValidDomainName(to))
|
|
||||||
) {
|
|
||||||
toError = isDefaultMetaMaskChain(chainId)
|
|
||||||
? INVALID_RECIPIENT_ADDRESS_ERROR
|
|
||||||
: INVALID_RECIPIENT_ADDRESS_NOT_ETH_NETWORK_ERROR;
|
|
||||||
} else if (isOriginContractAddress(to, sendTokenAddress)) {
|
|
||||||
toError = CONTRACT_ADDRESS_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { to: toError };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getToWarningObject(to, tokens = [], sendToken = null) {
|
|
||||||
let toWarning = null;
|
|
||||||
if (
|
|
||||||
sendToken &&
|
|
||||||
(toChecksumHexAddress(to) in contractMap ||
|
|
||||||
checkExistingAddresses(to, tokens))
|
|
||||||
) {
|
|
||||||
toWarning = KNOWN_RECIPIENT_ADDRESS_ERROR;
|
|
||||||
} else if (isValidDomainName(to) && isConfusing(to)) {
|
|
||||||
toWarning = CONFUSING_ENS_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { to: toWarning };
|
|
||||||
}
|
|
@ -1,115 +0,0 @@
|
|||||||
import {
|
|
||||||
REQUIRED_ERROR,
|
|
||||||
INVALID_RECIPIENT_ADDRESS_ERROR,
|
|
||||||
KNOWN_RECIPIENT_ADDRESS_ERROR,
|
|
||||||
CONFUSING_ENS_ERROR,
|
|
||||||
CONTRACT_ADDRESS_ERROR,
|
|
||||||
} from '../../send.constants';
|
|
||||||
import { getToErrorObject, getToWarningObject } from './add-recipient';
|
|
||||||
|
|
||||||
jest.mock('../../../../helpers/utils/util', () => ({
|
|
||||||
isDefaultMetaMaskChain: jest.fn().mockReturnValue(true),
|
|
||||||
isEthNetwork: jest.fn().mockReturnValue(true),
|
|
||||||
checkExistingAddresses: jest.fn().mockReturnValue(true),
|
|
||||||
isValidDomainName: jest.requireActual('../../../../helpers/utils/util')
|
|
||||||
.isValidDomainName,
|
|
||||||
isOriginContractAddress: jest.requireActual('../../../../helpers/utils/util')
|
|
||||||
.isOriginContractAddress,
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('../../../../../shared/modules/hexstring-utils', () => ({
|
|
||||||
isValidHexAddress: jest.fn((to) =>
|
|
||||||
Boolean(to.match(/^[0xabcdef123456798]+$/u)),
|
|
||||||
),
|
|
||||||
isBurnAddress: jest.fn(() => false),
|
|
||||||
toChecksumHexAddress: jest.fn((input) => input),
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe('add-recipient utils', () => {
|
|
||||||
describe('getToErrorObject()', () => {
|
|
||||||
it('should return a required error if "to" is falsy', () => {
|
|
||||||
expect(getToErrorObject(null)).toStrictEqual({
|
|
||||||
to: REQUIRED_ERROR,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return an invalid recipient error if "to" is truthy but invalid', () => {
|
|
||||||
expect(getToErrorObject('mockInvalidTo')).toStrictEqual({
|
|
||||||
to: INVALID_RECIPIENT_ADDRESS_ERROR,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return null if "to" is truthy and valid', () => {
|
|
||||||
expect(getToErrorObject('0xabc123')).toStrictEqual({
|
|
||||||
to: null,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a contract address error if the recipient is the same as the tokens contract address', () => {
|
|
||||||
expect(getToErrorObject('0xabc123', '0xabc123')).toStrictEqual({
|
|
||||||
to: CONTRACT_ADDRESS_ERROR,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return null if the recipient address is not the token contract address', () => {
|
|
||||||
expect(getToErrorObject('0xabc123', '0xabc456')).toStrictEqual({
|
|
||||||
to: null,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getToWarningObject()', () => {
|
|
||||||
it('should return a known address recipient error if "to" is a token address', () => {
|
|
||||||
expect(
|
|
||||||
getToWarningObject('0xabc123', [{ address: '0xabc123' }], {
|
|
||||||
address: '0xabc123',
|
|
||||||
}),
|
|
||||||
).toStrictEqual({
|
|
||||||
to: KNOWN_RECIPIENT_ADDRESS_ERROR,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should null if "to" is a token address but sendToken is falsy', () => {
|
|
||||||
expect(
|
|
||||||
getToWarningObject('0xabc123', [{ address: '0xabc123' }]),
|
|
||||||
).toStrictEqual({
|
|
||||||
to: null,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a known address recipient error if "to" is part of contract metadata', () => {
|
|
||||||
expect(
|
|
||||||
getToWarningObject(
|
|
||||||
'0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359',
|
|
||||||
[{ address: '0xabc123' }],
|
|
||||||
{ address: '0xabc123' },
|
|
||||||
),
|
|
||||||
).toStrictEqual({
|
|
||||||
to: KNOWN_RECIPIENT_ADDRESS_ERROR,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('should null if "to" is part of contract metadata but sendToken is falsy', () => {
|
|
||||||
expect(
|
|
||||||
getToWarningObject(
|
|
||||||
'0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359',
|
|
||||||
[{ address: '0xabc123' }],
|
|
||||||
{ address: '0xabc123' },
|
|
||||||
),
|
|
||||||
).toStrictEqual({
|
|
||||||
to: KNOWN_RECIPIENT_ADDRESS_ERROR,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should warn if name is a valid domain and confusable', () => {
|
|
||||||
expect(getToWarningObject('demo.eth')).toStrictEqual({
|
|
||||||
to: CONFUSING_ENS_ERROR,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not warn if name is a valid domain and not confusable', () => {
|
|
||||||
expect(getToWarningObject('vitalik.eth')).toStrictEqual({
|
|
||||||
to: null,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -2,145 +2,38 @@ import React, { Component } from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
|
||||||
import { debounce } from 'lodash';
|
|
||||||
import copyToClipboard from 'copy-to-clipboard/index';
|
|
||||||
import ENS from 'ethjs-ens';
|
|
||||||
import networkMap from 'ethereum-ens-network-map';
|
|
||||||
import log from 'loglevel';
|
|
||||||
import { isHexString } from 'ethereumjs-util';
|
|
||||||
import { ellipsify } from '../../send.utils';
|
import { ellipsify } from '../../send.utils';
|
||||||
import { isValidDomainName } from '../../../../helpers/utils/util';
|
import { isValidDomainName } from '../../../../helpers/utils/util';
|
||||||
import { MAINNET_NETWORK_ID } from '../../../../../shared/constants/network';
|
|
||||||
import {
|
import {
|
||||||
isBurnAddress,
|
isBurnAddress,
|
||||||
isValidHexAddress,
|
isValidHexAddress,
|
||||||
} from '../../../../../shared/modules/hexstring-utils';
|
} from '../../../../../shared/modules/hexstring-utils';
|
||||||
|
|
||||||
// Local Constants
|
|
||||||
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
|
|
||||||
const ZERO_X_ERROR_ADDRESS = '0x';
|
|
||||||
|
|
||||||
export default class EnsInput extends Component {
|
export default class EnsInput extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
t: PropTypes.func,
|
t: PropTypes.func,
|
||||||
|
metricsEvent: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
network: PropTypes.string,
|
|
||||||
selectedAddress: PropTypes.string,
|
selectedAddress: PropTypes.string,
|
||||||
selectedName: PropTypes.string,
|
selectedName: PropTypes.string,
|
||||||
onChange: PropTypes.func,
|
|
||||||
updateEnsResolution: PropTypes.func,
|
|
||||||
scanQrCode: PropTypes.func,
|
scanQrCode: PropTypes.func,
|
||||||
updateEnsResolutionError: PropTypes.func,
|
|
||||||
onPaste: PropTypes.func,
|
onPaste: PropTypes.func,
|
||||||
onReset: PropTypes.func,
|
|
||||||
onValidAddressTyped: PropTypes.func,
|
onValidAddressTyped: PropTypes.func,
|
||||||
contact: PropTypes.object,
|
|
||||||
value: PropTypes.string,
|
|
||||||
internalSearch: PropTypes.bool,
|
internalSearch: PropTypes.bool,
|
||||||
};
|
userInput: PropTypes.string,
|
||||||
|
onChange: PropTypes.func.isRequired,
|
||||||
state = {
|
onReset: PropTypes.func.isRequired,
|
||||||
input: '',
|
lookupEnsName: PropTypes.func.isRequired,
|
||||||
toError: null,
|
initializeEnsSlice: PropTypes.func.isRequired,
|
||||||
ensResolution: undefined,
|
resetEnsResolution: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { network, internalSearch } = this.props;
|
this.props.initializeEnsSlice();
|
||||||
const networkHasEnsSupport = getNetworkEnsSupport(network);
|
|
||||||
this.setState({ ensResolution: ZERO_ADDRESS });
|
|
||||||
|
|
||||||
if (networkHasEnsSupport && !internalSearch) {
|
|
||||||
const provider = global.ethereumProvider;
|
|
||||||
this.ens = new ENS({ provider, network });
|
|
||||||
this.checkName = debounce(this.lookupEnsName, 200);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
|
||||||
const { input } = this.state;
|
|
||||||
const { network, value, internalSearch } = this.props;
|
|
||||||
|
|
||||||
let newValue;
|
|
||||||
|
|
||||||
// Set the value of our input based on QR code provided by parent
|
|
||||||
const newProvidedValue = input !== value && prevProps.value !== value;
|
|
||||||
if (newProvidedValue) {
|
|
||||||
newValue = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prevProps.network !== network) {
|
|
||||||
if (getNetworkEnsSupport(network)) {
|
|
||||||
const provider = global.ethereumProvider;
|
|
||||||
this.ens = new ENS({ provider, network });
|
|
||||||
this.checkName = debounce(this.lookupEnsName, 200);
|
|
||||||
if (!newProvidedValue) {
|
|
||||||
newValue = input;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// ens is null on mount on a network that does not have ens support
|
|
||||||
// this is intended to prevent accidental lookup of domains across
|
|
||||||
// networks
|
|
||||||
this.ens = null;
|
|
||||||
this.checkName = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newValue !== undefined) {
|
|
||||||
this.onChange({ target: { value: newValue } });
|
|
||||||
}
|
|
||||||
if (!internalSearch && prevProps.internalSearch) {
|
|
||||||
this.resetInput();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resetInput = () => {
|
|
||||||
const {
|
|
||||||
updateEnsResolution,
|
|
||||||
updateEnsResolutionError,
|
|
||||||
onReset,
|
|
||||||
} = this.props;
|
|
||||||
this.onChange({ target: { value: '' } });
|
|
||||||
onReset();
|
|
||||||
updateEnsResolution('');
|
|
||||||
updateEnsResolutionError('');
|
|
||||||
};
|
|
||||||
|
|
||||||
lookupEnsName = (ensName) => {
|
|
||||||
const { network } = this.props;
|
|
||||||
const recipient = ensName.trim();
|
|
||||||
|
|
||||||
log.info(`ENS attempting to resolve name: ${recipient}`);
|
|
||||||
this.ens
|
|
||||||
.lookup(recipient)
|
|
||||||
.then((address) => {
|
|
||||||
if (address === ZERO_ADDRESS) {
|
|
||||||
throw new Error(this.context.t('noAddressForName'));
|
|
||||||
}
|
|
||||||
if (address === ZERO_X_ERROR_ADDRESS) {
|
|
||||||
throw new Error(this.context.t('ensRegistrationError'));
|
|
||||||
}
|
|
||||||
this.props.updateEnsResolution(address);
|
|
||||||
})
|
|
||||||
.catch((reason) => {
|
|
||||||
if (
|
|
||||||
isValidDomainName(recipient) &&
|
|
||||||
reason.message === 'ENS name not defined.'
|
|
||||||
) {
|
|
||||||
this.props.updateEnsResolutionError(
|
|
||||||
network === MAINNET_NETWORK_ID
|
|
||||||
? this.context.t('noAddressForName')
|
|
||||||
: this.context.t('ensNotFoundOnCurrentNetwork'),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
log.error(reason);
|
|
||||||
this.props.updateEnsResolutionError(reason.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
onPaste = (event) => {
|
onPaste = (event) => {
|
||||||
event.clipboardData.items[0].getAsString((text) => {
|
event.clipboardData.items[0].getAsString((text) => {
|
||||||
@ -155,40 +48,23 @@ export default class EnsInput extends Component {
|
|||||||
|
|
||||||
onChange = (e) => {
|
onChange = (e) => {
|
||||||
const {
|
const {
|
||||||
network,
|
|
||||||
onChange,
|
|
||||||
updateEnsResolution,
|
|
||||||
updateEnsResolutionError,
|
|
||||||
onValidAddressTyped,
|
onValidAddressTyped,
|
||||||
internalSearch,
|
internalSearch,
|
||||||
|
onChange,
|
||||||
|
lookupEnsName,
|
||||||
|
resetEnsResolution,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const input = e.target.value;
|
const input = e.target.value;
|
||||||
const networkHasEnsSupport = getNetworkEnsSupport(network);
|
|
||||||
|
|
||||||
this.setState({ input }, () => onChange(input));
|
onChange(input);
|
||||||
if (internalSearch) {
|
if (internalSearch) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// Empty ENS state if input is empty
|
// Empty ENS state if input is empty
|
||||||
// maybe scan ENS
|
// maybe scan ENS
|
||||||
|
|
||||||
if (
|
|
||||||
!networkHasEnsSupport &&
|
|
||||||
!(
|
|
||||||
isBurnAddress(input) === false &&
|
|
||||||
isValidHexAddress(input, { mixedCaseUseChecksum: true })
|
|
||||||
) &&
|
|
||||||
!isHexString(input)
|
|
||||||
) {
|
|
||||||
updateEnsResolution('');
|
|
||||||
updateEnsResolutionError(
|
|
||||||
networkHasEnsSupport ? '' : 'Network does not support ENS',
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isValidDomainName(input)) {
|
if (isValidDomainName(input)) {
|
||||||
this.lookupEnsName(input);
|
lookupEnsName(input);
|
||||||
} else if (
|
} else if (
|
||||||
onValidAddressTyped &&
|
onValidAddressTyped &&
|
||||||
!isBurnAddress(input) &&
|
!isBurnAddress(input) &&
|
||||||
@ -196,20 +72,16 @@ export default class EnsInput extends Component {
|
|||||||
) {
|
) {
|
||||||
onValidAddressTyped(input);
|
onValidAddressTyped(input);
|
||||||
} else {
|
} else {
|
||||||
updateEnsResolution('');
|
resetEnsResolution();
|
||||||
updateEnsResolutionError('');
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { t } = this.context;
|
const { t } = this.context;
|
||||||
const { className, selectedAddress } = this.props;
|
const { className, selectedAddress, selectedName, userInput } = this.props;
|
||||||
const { input } = this.state;
|
|
||||||
|
|
||||||
if (selectedAddress) {
|
const hasSelectedAddress = Boolean(selectedAddress);
|
||||||
return this.renderSelected();
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classnames('ens-input', className)}>
|
<div className={classnames('ens-input', className)}>
|
||||||
@ -217,61 +89,21 @@ export default class EnsInput extends Component {
|
|||||||
className={classnames('ens-input__wrapper', {
|
className={classnames('ens-input__wrapper', {
|
||||||
'ens-input__wrapper__status-icon--error': false,
|
'ens-input__wrapper__status-icon--error': false,
|
||||||
'ens-input__wrapper__status-icon--valid': false,
|
'ens-input__wrapper__status-icon--valid': false,
|
||||||
|
'ens-input__wrapper--valid': hasSelectedAddress,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div className="ens-input__wrapper__status-icon" />
|
|
||||||
<input
|
|
||||||
className="ens-input__wrapper__input"
|
|
||||||
type="text"
|
|
||||||
dir="auto"
|
|
||||||
placeholder={t('recipientAddressPlaceholder')}
|
|
||||||
onChange={this.onChange}
|
|
||||||
onPaste={this.onPaste}
|
|
||||||
value={selectedAddress || input}
|
|
||||||
autoFocus
|
|
||||||
data-testid="ens-input"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
className={classnames('ens-input__wrapper__action-icon', {
|
|
||||||
'ens-input__wrapper__action-icon--erase': input,
|
|
||||||
'ens-input__wrapper__action-icon--qrcode': !input,
|
|
||||||
})}
|
|
||||||
onClick={() => {
|
|
||||||
if (input) {
|
|
||||||
this.resetInput();
|
|
||||||
} else {
|
|
||||||
this.props.scanQrCode();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderSelected() {
|
|
||||||
const { t } = this.context;
|
|
||||||
const {
|
|
||||||
className,
|
|
||||||
selectedAddress,
|
|
||||||
selectedName,
|
|
||||||
contact = {},
|
|
||||||
} = this.props;
|
|
||||||
const name = contact.name || selectedName;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classnames('ens-input', className)}>
|
|
||||||
<div className="ens-input__wrapper ens-input__wrapper--valid">
|
|
||||||
<div className="ens-input__wrapper__status-icon ens-input__wrapper__status-icon--valid" />
|
|
||||||
<div
|
<div
|
||||||
className="ens-input__wrapper__input ens-input__wrapper__input--selected"
|
className={classnames('ens-input__wrapper__status-icon', {
|
||||||
placeholder={t('recipientAddress')}
|
'ens-input__wrapper__status-icon--valid': hasSelectedAddress,
|
||||||
onChange={this.onChange}
|
})}
|
||||||
>
|
/>
|
||||||
|
{hasSelectedAddress ? (
|
||||||
|
<>
|
||||||
|
<div className="ens-input__wrapper__input ens-input__wrapper__input--selected">
|
||||||
<div className="ens-input__selected-input__title">
|
<div className="ens-input__selected-input__title">
|
||||||
{name || ellipsify(selectedAddress)}
|
{selectedName || ellipsify(selectedAddress)}
|
||||||
</div>
|
</div>
|
||||||
{name && (
|
{selectedName && (
|
||||||
<div className="ens-input__selected-input__subtitle">
|
<div className="ens-input__selected-input__subtitle">
|
||||||
{selectedAddress}
|
{selectedAddress}
|
||||||
</div>
|
</div>
|
||||||
@ -279,73 +111,39 @@ export default class EnsInput extends Component {
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="ens-input__wrapper__action-icon ens-input__wrapper__action-icon--erase"
|
className="ens-input__wrapper__action-icon ens-input__wrapper__action-icon--erase"
|
||||||
onClick={this.resetInput}
|
onClick={this.props.onReset}
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<input
|
||||||
|
className="ens-input__wrapper__input"
|
||||||
|
type="text"
|
||||||
|
dir="auto"
|
||||||
|
placeholder={t('recipientAddressPlaceholder')}
|
||||||
|
onChange={this.onChange}
|
||||||
|
onPaste={this.onPaste}
|
||||||
|
value={selectedAddress || userInput}
|
||||||
|
autoFocus
|
||||||
|
data-testid="ens-input"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className={classnames('ens-input__wrapper__action-icon', {
|
||||||
|
'ens-input__wrapper__action-icon--erase': userInput,
|
||||||
|
'ens-input__wrapper__action-icon--qrcode': !userInput,
|
||||||
|
})}
|
||||||
|
onClick={() => {
|
||||||
|
if (userInput) {
|
||||||
|
this.props.onReset();
|
||||||
|
} else {
|
||||||
|
this.props.scanQrCode();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ensIcon(recipient) {
|
|
||||||
const { hoverText } = this.state;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
className="#ensIcon"
|
|
||||||
title={hoverText}
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
top: '16px',
|
|
||||||
left: '-25px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{this.ensIconContents(recipient)}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ensIconContents() {
|
|
||||||
const { loadingEns, ensFailure, ensResolution, toError } = this.state;
|
|
||||||
|
|
||||||
if (toError) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loadingEns) {
|
|
||||||
return (
|
|
||||||
<img
|
|
||||||
src="images/loading.svg"
|
|
||||||
style={{
|
|
||||||
width: '30px',
|
|
||||||
height: '30px',
|
|
||||||
transform: 'translateY(-6px)',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ensFailure) {
|
|
||||||
return <i className="fa fa-warning fa-lg warning" />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ensResolution && ensResolution !== ZERO_ADDRESS) {
|
|
||||||
return (
|
|
||||||
<i
|
|
||||||
className="fa fa-check-circle fa-lg cursor-pointer"
|
|
||||||
style={{ color: 'green' }}
|
|
||||||
onClick={(event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
copyToClipboard(ensResolution);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getNetworkEnsSupport(network) {
|
|
||||||
return Boolean(networkMap[network]);
|
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,18 @@
|
|||||||
|
import { debounce } from 'lodash';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { CHAIN_ID_TO_NETWORK_ID_MAP } from '../../../../../shared/constants/network';
|
|
||||||
import {
|
import {
|
||||||
getSendTo,
|
lookupEnsName,
|
||||||
getSendToNickname,
|
initializeEnsSlice,
|
||||||
getAddressBookEntry,
|
resetResolution,
|
||||||
getCurrentChainId,
|
} from '../../../../ducks/ens';
|
||||||
} from '../../../../selectors';
|
|
||||||
import EnsInput from './ens-input.component';
|
import EnsInput from './ens-input.component';
|
||||||
|
|
||||||
export default connect((state) => {
|
function mapDispatchToProps(dispatch) {
|
||||||
const selectedAddress = getSendTo(state);
|
|
||||||
const chainId = getCurrentChainId(state);
|
|
||||||
return {
|
return {
|
||||||
network: CHAIN_ID_TO_NETWORK_ID_MAP[chainId],
|
lookupEnsName: debounce((ensName) => dispatch(lookupEnsName(ensName)), 150),
|
||||||
selectedAddress,
|
initializeEnsSlice: () => dispatch(initializeEnsSlice()),
|
||||||
selectedName: getSendToNickname(state),
|
resetEnsResolution: debounce(() => dispatch(resetResolution()), 300),
|
||||||
contact: getAddressBookEntry(state, selectedAddress),
|
|
||||||
};
|
};
|
||||||
})(EnsInput);
|
}
|
||||||
|
|
||||||
|
export default connect(null, mapDispatchToProps)(EnsInput);
|
||||||
|
@ -1,93 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { shallow } from 'enzyme';
|
|
||||||
import sinon from 'sinon';
|
|
||||||
import AmountMaxButton from './amount-max-button.component';
|
|
||||||
|
|
||||||
describe('AmountMaxButton Component', () => {
|
|
||||||
let wrapper;
|
|
||||||
let instance;
|
|
||||||
|
|
||||||
const propsMethodSpies = {
|
|
||||||
setAmountToMax: sinon.spy(),
|
|
||||||
setMaxModeTo: sinon.spy(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const MOCK_EVENT = { preventDefault: () => undefined };
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
sinon.spy(AmountMaxButton.prototype, 'setMaxAmount');
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
wrapper = shallow(
|
|
||||||
<AmountMaxButton
|
|
||||||
balance="mockBalance"
|
|
||||||
gasTotal="mockGasTotal"
|
|
||||||
maxModeOn={false}
|
|
||||||
sendToken={{ address: 'mockTokenAddress' }}
|
|
||||||
setAmountToMax={propsMethodSpies.setAmountToMax}
|
|
||||||
setMaxModeTo={propsMethodSpies.setMaxModeTo}
|
|
||||||
tokenBalance="mockTokenBalance"
|
|
||||||
/>,
|
|
||||||
{
|
|
||||||
context: {
|
|
||||||
t: (str) => `${str}_t`,
|
|
||||||
metricsEvent: () => undefined,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
instance = wrapper.instance();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
propsMethodSpies.setAmountToMax.resetHistory();
|
|
||||||
propsMethodSpies.setMaxModeTo.resetHistory();
|
|
||||||
AmountMaxButton.prototype.setMaxAmount.resetHistory();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
sinon.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('setMaxAmount', () => {
|
|
||||||
it('should call setAmountToMax with the correct params', () => {
|
|
||||||
expect(propsMethodSpies.setAmountToMax.callCount).toStrictEqual(0);
|
|
||||||
instance.setMaxAmount();
|
|
||||||
expect(propsMethodSpies.setAmountToMax.callCount).toStrictEqual(1);
|
|
||||||
expect(propsMethodSpies.setAmountToMax.getCall(0).args).toStrictEqual([
|
|
||||||
{
|
|
||||||
balance: 'mockBalance',
|
|
||||||
gasTotal: 'mockGasTotal',
|
|
||||||
sendToken: { address: 'mockTokenAddress' },
|
|
||||||
tokenBalance: 'mockTokenBalance',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('render', () => {
|
|
||||||
it('should render an element with a send-v2__amount-max class', () => {
|
|
||||||
expect(wrapper.find('.send-v2__amount-max')).toHaveLength(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call setMaxModeTo and setMaxAmount when the checkbox is checked', () => {
|
|
||||||
const { onClick } = wrapper.find('.send-v2__amount-max').props();
|
|
||||||
|
|
||||||
expect(AmountMaxButton.prototype.setMaxAmount.callCount).toStrictEqual(0);
|
|
||||||
expect(propsMethodSpies.setMaxModeTo.callCount).toStrictEqual(0);
|
|
||||||
onClick(MOCK_EVENT);
|
|
||||||
expect(AmountMaxButton.prototype.setMaxAmount.callCount).toStrictEqual(1);
|
|
||||||
expect(propsMethodSpies.setMaxModeTo.callCount).toStrictEqual(1);
|
|
||||||
expect(propsMethodSpies.setMaxModeTo.getCall(0).args).toStrictEqual([
|
|
||||||
true,
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should render the expected text when maxModeOn is false', () => {
|
|
||||||
wrapper.setProps({ maxModeOn: false });
|
|
||||||
expect(wrapper.find('.send-v2__amount-max').text()).toStrictEqual(
|
|
||||||
'max_t',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,42 +0,0 @@
|
|||||||
import { connect } from 'react-redux';
|
|
||||||
import {
|
|
||||||
getGasTotal,
|
|
||||||
getSendToken,
|
|
||||||
getSendFromBalance,
|
|
||||||
getTokenBalance,
|
|
||||||
getSendMaxModeState,
|
|
||||||
getBasicGasEstimateLoadingStatus,
|
|
||||||
} from '../../../../../selectors';
|
|
||||||
import {
|
|
||||||
updateSendErrors,
|
|
||||||
updateSendAmount,
|
|
||||||
setMaxModeTo,
|
|
||||||
} from '../../../../../ducks/send/send.duck';
|
|
||||||
import { calcMaxAmount } from './amount-max-button.utils';
|
|
||||||
import AmountMaxButton from './amount-max-button.component';
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(AmountMaxButton);
|
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
|
||||||
return {
|
|
||||||
balance: getSendFromBalance(state),
|
|
||||||
buttonDataLoading: getBasicGasEstimateLoadingStatus(state),
|
|
||||||
gasTotal: getGasTotal(state),
|
|
||||||
maxModeOn: getSendMaxModeState(state),
|
|
||||||
sendToken: getSendToken(state),
|
|
||||||
tokenBalance: getTokenBalance(state),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
|
||||||
return {
|
|
||||||
setAmountToMax: (maxAmountDataObject) => {
|
|
||||||
dispatch(updateSendErrors({ amount: null }));
|
|
||||||
dispatch(updateSendAmount(calcMaxAmount(maxAmountDataObject)));
|
|
||||||
},
|
|
||||||
clearMaxAmount: () => {
|
|
||||||
dispatch(updateSendAmount('0'));
|
|
||||||
},
|
|
||||||
setMaxModeTo: (bool) => dispatch(setMaxModeTo(bool)),
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,83 +0,0 @@
|
|||||||
import sinon from 'sinon';
|
|
||||||
|
|
||||||
import {
|
|
||||||
updateSendErrors,
|
|
||||||
setMaxModeTo,
|
|
||||||
updateSendAmount,
|
|
||||||
} from '../../../../../ducks/send/send.duck';
|
|
||||||
|
|
||||||
let mapStateToProps;
|
|
||||||
let mapDispatchToProps;
|
|
||||||
|
|
||||||
jest.mock('react-redux', () => ({
|
|
||||||
connect: (ms, md) => {
|
|
||||||
mapStateToProps = ms;
|
|
||||||
mapDispatchToProps = md;
|
|
||||||
return () => ({});
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('../../../../../selectors', () => ({
|
|
||||||
getGasTotal: (s) => `mockGasTotal:${s}`,
|
|
||||||
getSendToken: (s) => `mockSendToken:${s}`,
|
|
||||||
getSendFromBalance: (s) => `mockBalance:${s}`,
|
|
||||||
getTokenBalance: (s) => `mockTokenBalance:${s}`,
|
|
||||||
getSendMaxModeState: (s) => `mockMaxModeOn:${s}`,
|
|
||||||
getBasicGasEstimateLoadingStatus: (s) => `mockButtonDataLoading:${s}`,
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('./amount-max-button.utils.js', () => ({
|
|
||||||
calcMaxAmount: (mockObj) => mockObj.val + 1,
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('../../../../../ducks/send/send.duck', () => ({
|
|
||||||
setMaxModeTo: jest.fn(),
|
|
||||||
updateSendAmount: jest.fn(),
|
|
||||||
updateSendErrors: jest.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
require('./amount-max-button.container.js');
|
|
||||||
|
|
||||||
describe('amount-max-button container', () => {
|
|
||||||
describe('mapStateToProps()', () => {
|
|
||||||
it('should map the correct properties to props', () => {
|
|
||||||
expect(mapStateToProps('mockState')).toStrictEqual({
|
|
||||||
balance: 'mockBalance:mockState',
|
|
||||||
buttonDataLoading: 'mockButtonDataLoading:mockState',
|
|
||||||
gasTotal: 'mockGasTotal:mockState',
|
|
||||||
maxModeOn: 'mockMaxModeOn:mockState',
|
|
||||||
sendToken: 'mockSendToken:mockState',
|
|
||||||
tokenBalance: 'mockTokenBalance:mockState',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('mapDispatchToProps()', () => {
|
|
||||||
let dispatchSpy;
|
|
||||||
let mapDispatchToPropsObject;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
dispatchSpy = sinon.spy();
|
|
||||||
mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('setAmountToMax()', () => {
|
|
||||||
it('should dispatch an action', () => {
|
|
||||||
mapDispatchToPropsObject.setAmountToMax({ val: 11, foo: 'bar' });
|
|
||||||
expect(dispatchSpy.calledTwice).toStrictEqual(true);
|
|
||||||
expect(updateSendErrors).toHaveBeenCalled();
|
|
||||||
expect(updateSendErrors).toHaveBeenCalledWith({ amount: null });
|
|
||||||
expect(updateSendAmount).toHaveBeenCalled();
|
|
||||||
expect(updateSendAmount).toHaveBeenCalledWith(12);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('setMaxModeTo()', () => {
|
|
||||||
it('should dispatch an action', () => {
|
|
||||||
mapDispatchToPropsObject.setMaxModeTo('mockVal');
|
|
||||||
expect(dispatchSpy.calledOnce).toStrictEqual(true);
|
|
||||||
expect(setMaxModeTo).toHaveBeenCalledWith('mockVal');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -0,0 +1,49 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { getBasicGasEstimateLoadingStatus } from '../../../../../selectors';
|
||||||
|
import {
|
||||||
|
getSendMaxModeState,
|
||||||
|
isSendFormInvalid,
|
||||||
|
toggleSendMaxMode,
|
||||||
|
} from '../../../../../ducks/send';
|
||||||
|
import { useI18nContext } from '../../../../../hooks/useI18nContext';
|
||||||
|
import { useMetricEvent } from '../../../../../hooks/useMetricEvent';
|
||||||
|
|
||||||
|
export default function AmountMaxButton() {
|
||||||
|
const buttonDataLoading = useSelector(getBasicGasEstimateLoadingStatus);
|
||||||
|
const isDraftTransactionInvalid = useSelector(isSendFormInvalid);
|
||||||
|
const maxModeOn = useSelector(getSendMaxModeState);
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const trackClickedMax = useMetricEvent({
|
||||||
|
eventOpts: {
|
||||||
|
category: 'Transactions',
|
||||||
|
action: 'Edit Screen',
|
||||||
|
name: 'Clicked "Amount Max"',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const t = useI18nContext();
|
||||||
|
|
||||||
|
const onMaxClick = () => {
|
||||||
|
trackClickedMax();
|
||||||
|
dispatch(toggleSendMaxMode());
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className="send-v2__amount-max"
|
||||||
|
disabled={buttonDataLoading || isDraftTransactionInvalid}
|
||||||
|
onClick={onMaxClick}
|
||||||
|
>
|
||||||
|
<input type="checkbox" checked={maxModeOn} readOnly />
|
||||||
|
<div
|
||||||
|
className={classnames('send-v2__amount-max__button', {
|
||||||
|
'send-v2__amount-max__button__disabled':
|
||||||
|
buttonDataLoading || isDraftTransactionInvalid,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{t('max')}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import configureMockStore from 'redux-mock-store';
|
||||||
|
import thunk from 'redux-thunk';
|
||||||
|
|
||||||
|
import { fireEvent } from '@testing-library/react';
|
||||||
|
import { initialState, SEND_STATUSES } from '../../../../../ducks/send';
|
||||||
|
import { renderWithProvider } from '../../../../../../test/jest';
|
||||||
|
import AmountMaxButton from './amount-max-button';
|
||||||
|
|
||||||
|
const middleware = [thunk];
|
||||||
|
|
||||||
|
describe('AmountMaxButton Component', () => {
|
||||||
|
describe('render', () => {
|
||||||
|
it('should render a "Max" button', () => {
|
||||||
|
const { getByText } = renderWithProvider(
|
||||||
|
<AmountMaxButton />,
|
||||||
|
configureMockStore(middleware)({
|
||||||
|
send: initialState,
|
||||||
|
gas: { basicEstimateStatus: 'LOADING' },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(getByText('Max')).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should dispatch action to set mode to MAX', () => {
|
||||||
|
const store = configureMockStore(middleware)({
|
||||||
|
send: { ...initialState, status: SEND_STATUSES.VALID },
|
||||||
|
gas: { basicEstimateStatus: 'READY' },
|
||||||
|
});
|
||||||
|
const { getByText } = renderWithProvider(<AmountMaxButton />, store);
|
||||||
|
|
||||||
|
const expectedActions = [
|
||||||
|
{ type: 'send/updateAmountMode', payload: 'MAX' },
|
||||||
|
];
|
||||||
|
|
||||||
|
fireEvent.click(getByText('Max'), { bubbles: true });
|
||||||
|
const actions = store.getActions();
|
||||||
|
expect(actions).toStrictEqual(expectedActions);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should dispatch action to set amount mode to INPUT', () => {
|
||||||
|
const store = configureMockStore(middleware)({
|
||||||
|
send: {
|
||||||
|
...initialState,
|
||||||
|
status: SEND_STATUSES.VALID,
|
||||||
|
amount: { ...initialState.amount, mode: 'MAX' },
|
||||||
|
},
|
||||||
|
gas: { basicEstimateStatus: 'READY' },
|
||||||
|
});
|
||||||
|
const { getByText } = renderWithProvider(<AmountMaxButton />, store);
|
||||||
|
|
||||||
|
const expectedActions = [
|
||||||
|
{ type: 'send/updateAmountMode', payload: 'INPUT' },
|
||||||
|
];
|
||||||
|
|
||||||
|
fireEvent.click(getByText('Max'), { bubbles: true });
|
||||||
|
const actions = store.getActions();
|
||||||
|
expect(actions).toStrictEqual(expectedActions);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -1,22 +0,0 @@
|
|||||||
import {
|
|
||||||
multiplyCurrencies,
|
|
||||||
subtractCurrencies,
|
|
||||||
} from '../../../../../helpers/utils/conversion-util';
|
|
||||||
import { addHexPrefix } from '../../../../../../app/scripts/lib/util';
|
|
||||||
|
|
||||||
export function calcMaxAmount({ balance, gasTotal, sendToken, tokenBalance }) {
|
|
||||||
const { decimals } = sendToken || {};
|
|
||||||
const multiplier = Math.pow(10, Number(decimals || 0));
|
|
||||||
|
|
||||||
return sendToken
|
|
||||||
? multiplyCurrencies(tokenBalance, multiplier, {
|
|
||||||
toNumericBase: 'hex',
|
|
||||||
multiplicandBase: 16,
|
|
||||||
multiplierBase: 10,
|
|
||||||
})
|
|
||||||
: subtractCurrencies(addHexPrefix(balance), addHexPrefix(gasTotal), {
|
|
||||||
toNumericBase: 'hex',
|
|
||||||
aBase: 16,
|
|
||||||
bBase: 16,
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
import { calcMaxAmount } from './amount-max-button.utils';
|
|
||||||
|
|
||||||
describe('amount-max-button utils', () => {
|
|
||||||
describe('calcMaxAmount()', () => {
|
|
||||||
it('should calculate the correct amount when no sendToken defined', () => {
|
|
||||||
expect(
|
|
||||||
calcMaxAmount({
|
|
||||||
balance: 'ffffff',
|
|
||||||
gasTotal: 'ff',
|
|
||||||
sendToken: false,
|
|
||||||
}),
|
|
||||||
).toStrictEqual('ffff00');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should calculate the correct amount when a sendToken is defined', () => {
|
|
||||||
expect(
|
|
||||||
calcMaxAmount({
|
|
||||||
sendToken: {
|
|
||||||
decimals: 10,
|
|
||||||
},
|
|
||||||
tokenBalance: '64',
|
|
||||||
}),
|
|
||||||
).toStrictEqual('e8d4a51000');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1 +1 @@
|
|||||||
export { default } from './amount-max-button.container';
|
export { default } from './amount-max-button';
|
||||||
|
@ -1,111 +1,35 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { debounce } from 'lodash';
|
|
||||||
import SendRowWrapper from '../send-row-wrapper';
|
import SendRowWrapper from '../send-row-wrapper';
|
||||||
import UserPreferencedCurrencyInput from '../../../../components/app/user-preferenced-currency-input';
|
import UserPreferencedCurrencyInput from '../../../../components/app/user-preferenced-currency-input';
|
||||||
import UserPreferencedTokenInput from '../../../../components/app/user-preferenced-token-input';
|
import UserPreferencedTokenInput from '../../../../components/app/user-preferenced-token-input';
|
||||||
|
import { ASSET_TYPES } from '../../../../ducks/send';
|
||||||
import AmountMaxButton from './amount-max-button';
|
import AmountMaxButton from './amount-max-button';
|
||||||
|
|
||||||
export default class SendAmountRow extends Component {
|
export default class SendAmountRow extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
amount: PropTypes.string,
|
amount: PropTypes.string,
|
||||||
balance: PropTypes.string,
|
|
||||||
conversionRate: PropTypes.number,
|
|
||||||
gasTotal: PropTypes.string,
|
|
||||||
inError: PropTypes.bool,
|
inError: PropTypes.bool,
|
||||||
primaryCurrency: PropTypes.string,
|
asset: PropTypes.object,
|
||||||
sendToken: PropTypes.object,
|
|
||||||
setMaxModeTo: PropTypes.func,
|
|
||||||
tokenBalance: PropTypes.string,
|
|
||||||
updateGasFeeError: PropTypes.func,
|
|
||||||
updateSendAmount: PropTypes.func,
|
updateSendAmount: PropTypes.func,
|
||||||
updateSendAmountError: PropTypes.func,
|
|
||||||
updateGas: PropTypes.func,
|
|
||||||
maxModeOn: PropTypes.bool,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
t: PropTypes.func,
|
t: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
|
||||||
const { maxModeOn: prevMaxModeOn, gasTotal: prevGasTotal } = prevProps;
|
|
||||||
const { maxModeOn, amount, gasTotal, sendToken } = this.props;
|
|
||||||
|
|
||||||
if (maxModeOn && sendToken && !prevMaxModeOn) {
|
|
||||||
this.updateGas(amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prevGasTotal !== gasTotal) {
|
|
||||||
this.validateAmount(amount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateGas = debounce(this.updateGas.bind(this), 500);
|
|
||||||
|
|
||||||
validateAmount(amount) {
|
|
||||||
const {
|
|
||||||
balance,
|
|
||||||
conversionRate,
|
|
||||||
gasTotal,
|
|
||||||
primaryCurrency,
|
|
||||||
sendToken,
|
|
||||||
tokenBalance,
|
|
||||||
updateGasFeeError,
|
|
||||||
updateSendAmountError,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
updateSendAmountError({
|
|
||||||
amount,
|
|
||||||
balance,
|
|
||||||
conversionRate,
|
|
||||||
gasTotal,
|
|
||||||
primaryCurrency,
|
|
||||||
sendToken,
|
|
||||||
tokenBalance,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (sendToken) {
|
|
||||||
updateGasFeeError({
|
|
||||||
balance,
|
|
||||||
conversionRate,
|
|
||||||
gasTotal,
|
|
||||||
primaryCurrency,
|
|
||||||
sendToken,
|
|
||||||
tokenBalance,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateAmount(amount) {
|
|
||||||
const { updateSendAmount, setMaxModeTo } = this.props;
|
|
||||||
|
|
||||||
setMaxModeTo(false);
|
|
||||||
updateSendAmount(amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateGas(amount) {
|
|
||||||
const { sendToken, updateGas } = this.props;
|
|
||||||
|
|
||||||
if (sendToken) {
|
|
||||||
updateGas({ amount });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleChange = (newAmount) => {
|
handleChange = (newAmount) => {
|
||||||
this.validateAmount(newAmount);
|
this.props.updateSendAmount(newAmount);
|
||||||
this.updateGas(newAmount);
|
|
||||||
this.updateAmount(newAmount);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
renderInput() {
|
renderInput() {
|
||||||
const { amount, inError, sendToken } = this.props;
|
const { amount, inError, asset } = this.props;
|
||||||
|
|
||||||
return sendToken ? (
|
return asset.type === ASSET_TYPES.TOKEN ? (
|
||||||
<UserPreferencedTokenInput
|
<UserPreferencedTokenInput
|
||||||
error={inError}
|
error={inError}
|
||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
token={sendToken}
|
token={asset.details}
|
||||||
value={amount}
|
value={amount}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
@ -118,7 +42,7 @@ export default class SendAmountRow extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { gasTotal, inError } = this.props;
|
const { inError } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SendRowWrapper
|
<SendRowWrapper
|
||||||
@ -126,7 +50,7 @@ export default class SendAmountRow extends Component {
|
|||||||
showError={inError}
|
showError={inError}
|
||||||
errorType="amount"
|
errorType="amount"
|
||||||
>
|
>
|
||||||
{gasTotal && <AmountMaxButton inError={inError} />}
|
<AmountMaxButton inError={inError} />
|
||||||
{this.renderInput()}
|
{this.renderInput()}
|
||||||
</SendRowWrapper>
|
</SendRowWrapper>
|
||||||
);
|
);
|
||||||
|
@ -3,88 +3,13 @@ import { shallow } from 'enzyme';
|
|||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
import SendRowWrapper from '../send-row-wrapper/send-row-wrapper.component';
|
import SendRowWrapper from '../send-row-wrapper/send-row-wrapper.component';
|
||||||
import UserPreferencedTokenInput from '../../../../components/app/user-preferenced-token-input';
|
import UserPreferencedTokenInput from '../../../../components/app/user-preferenced-token-input';
|
||||||
|
import { ASSET_TYPES } from '../../../../ducks/send';
|
||||||
import SendAmountRow from './send-amount-row.component';
|
import SendAmountRow from './send-amount-row.component';
|
||||||
|
|
||||||
import AmountMaxButton from './amount-max-button/amount-max-button.container';
|
import AmountMaxButton from './amount-max-button/amount-max-button';
|
||||||
|
|
||||||
describe('SendAmountRow Component', () => {
|
describe('SendAmountRow Component', () => {
|
||||||
describe('validateAmount', () => {
|
|
||||||
it('should call updateSendAmountError with the correct params', () => {
|
|
||||||
const {
|
|
||||||
instance,
|
|
||||||
propsMethodSpies: { updateSendAmountError },
|
|
||||||
} = shallowRenderSendAmountRow();
|
|
||||||
|
|
||||||
expect(updateSendAmountError.callCount).toStrictEqual(0);
|
|
||||||
|
|
||||||
instance.validateAmount('someAmount');
|
|
||||||
|
|
||||||
expect(
|
|
||||||
updateSendAmountError.calledOnceWithExactly({
|
|
||||||
amount: 'someAmount',
|
|
||||||
balance: 'mockBalance',
|
|
||||||
conversionRate: 7,
|
|
||||||
gasTotal: 'mockGasTotal',
|
|
||||||
primaryCurrency: 'mockPrimaryCurrency',
|
|
||||||
sendToken: { address: 'mockTokenAddress' },
|
|
||||||
tokenBalance: 'mockTokenBalance',
|
|
||||||
}),
|
|
||||||
).toStrictEqual(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call updateGasFeeError if sendToken is truthy', () => {
|
|
||||||
const {
|
|
||||||
instance,
|
|
||||||
propsMethodSpies: { updateGasFeeError },
|
|
||||||
} = shallowRenderSendAmountRow();
|
|
||||||
|
|
||||||
expect(updateGasFeeError.callCount).toStrictEqual(0);
|
|
||||||
|
|
||||||
instance.validateAmount('someAmount');
|
|
||||||
|
|
||||||
expect(
|
|
||||||
updateGasFeeError.calledOnceWithExactly({
|
|
||||||
balance: 'mockBalance',
|
|
||||||
conversionRate: 7,
|
|
||||||
gasTotal: 'mockGasTotal',
|
|
||||||
primaryCurrency: 'mockPrimaryCurrency',
|
|
||||||
sendToken: { address: 'mockTokenAddress' },
|
|
||||||
tokenBalance: 'mockTokenBalance',
|
|
||||||
}),
|
|
||||||
).toStrictEqual(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call not updateGasFeeError if sendToken is falsey', () => {
|
|
||||||
const {
|
|
||||||
wrapper,
|
|
||||||
instance,
|
|
||||||
propsMethodSpies: { updateGasFeeError },
|
|
||||||
} = shallowRenderSendAmountRow();
|
|
||||||
|
|
||||||
wrapper.setProps({ sendToken: null });
|
|
||||||
|
|
||||||
expect(updateGasFeeError.callCount).toStrictEqual(0);
|
|
||||||
|
|
||||||
instance.validateAmount('someAmount');
|
|
||||||
|
|
||||||
expect(updateGasFeeError.callCount).toStrictEqual(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('updateAmount', () => {
|
describe('updateAmount', () => {
|
||||||
it('should call setMaxModeTo', () => {
|
|
||||||
const {
|
|
||||||
instance,
|
|
||||||
propsMethodSpies: { setMaxModeTo },
|
|
||||||
} = shallowRenderSendAmountRow();
|
|
||||||
|
|
||||||
expect(setMaxModeTo.callCount).toStrictEqual(0);
|
|
||||||
|
|
||||||
instance.updateAmount('someAmount');
|
|
||||||
|
|
||||||
expect(setMaxModeTo.calledOnceWithExactly(false)).toStrictEqual(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should call updateSendAmount', () => {
|
it('should call updateSendAmount', () => {
|
||||||
const {
|
const {
|
||||||
instance,
|
instance,
|
||||||
@ -93,7 +18,7 @@ describe('SendAmountRow Component', () => {
|
|||||||
|
|
||||||
expect(updateSendAmount.callCount).toStrictEqual(0);
|
expect(updateSendAmount.callCount).toStrictEqual(0);
|
||||||
|
|
||||||
instance.updateAmount('someAmount');
|
instance.handleChange('someAmount');
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
updateSendAmount.calledOnceWithExactly('someAmount'),
|
updateSendAmount.calledOnceWithExactly('someAmount'),
|
||||||
@ -136,10 +61,7 @@ describe('SendAmountRow Component', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should render the UserPreferencedTokenInput with the correct props', () => {
|
it('should render the UserPreferencedTokenInput with the correct props', () => {
|
||||||
const {
|
const { wrapper } = shallowRenderSendAmountRow();
|
||||||
wrapper,
|
|
||||||
instanceSpies: { updateGas, updateAmount, validateAmount },
|
|
||||||
} = shallowRenderSendAmountRow();
|
|
||||||
const { onChange, error, value } = wrapper
|
const { onChange, error, value } = wrapper
|
||||||
.find(SendRowWrapper)
|
.find(SendRowWrapper)
|
||||||
.childAt(1)
|
.childAt(1)
|
||||||
@ -147,67 +69,34 @@ describe('SendAmountRow Component', () => {
|
|||||||
|
|
||||||
expect(error).toStrictEqual(false);
|
expect(error).toStrictEqual(false);
|
||||||
expect(value).toStrictEqual('mockAmount');
|
expect(value).toStrictEqual('mockAmount');
|
||||||
expect(updateGas.callCount).toStrictEqual(0);
|
|
||||||
expect(updateAmount.callCount).toStrictEqual(0);
|
|
||||||
expect(validateAmount.callCount).toStrictEqual(0);
|
|
||||||
|
|
||||||
onChange('mockNewAmount');
|
onChange('mockNewAmount');
|
||||||
|
|
||||||
expect(updateGas.calledOnceWithExactly('mockNewAmount')).toStrictEqual(
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
expect(updateAmount.calledOnceWithExactly('mockNewAmount')).toStrictEqual(
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
expect(
|
|
||||||
validateAmount.calledOnceWithExactly('mockNewAmount'),
|
|
||||||
).toStrictEqual(true);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function shallowRenderSendAmountRow() {
|
function shallowRenderSendAmountRow() {
|
||||||
const setMaxModeTo = sinon.spy();
|
|
||||||
const updateGasFeeError = sinon.spy();
|
|
||||||
const updateSendAmount = sinon.spy();
|
const updateSendAmount = sinon.spy();
|
||||||
const updateSendAmountError = sinon.spy();
|
|
||||||
const wrapper = shallow(
|
const wrapper = shallow(
|
||||||
<SendAmountRow
|
<SendAmountRow
|
||||||
amount="mockAmount"
|
amount="mockAmount"
|
||||||
balance="mockBalance"
|
|
||||||
conversionRate={7}
|
|
||||||
convertedCurrency="mockConvertedCurrency"
|
|
||||||
gasTotal="mockGasTotal"
|
|
||||||
inError={false}
|
inError={false}
|
||||||
primaryCurrency="mockPrimaryCurrency"
|
asset={{
|
||||||
sendToken={{ address: 'mockTokenAddress' }}
|
type: ASSET_TYPES.TOKEN,
|
||||||
setMaxModeTo={setMaxModeTo}
|
balance: 'mockTokenBalance',
|
||||||
tokenBalance="mockTokenBalance"
|
details: { address: 'mockTokenAddress' },
|
||||||
updateGasFeeError={updateGasFeeError}
|
}}
|
||||||
updateSendAmount={updateSendAmount}
|
updateSendAmount={updateSendAmount}
|
||||||
updateSendAmountError={updateSendAmountError}
|
|
||||||
updateGas={() => undefined}
|
|
||||||
/>,
|
/>,
|
||||||
{ context: { t: (str) => `${str}_t` } },
|
{ context: { t: (str) => `${str}_t` } },
|
||||||
);
|
);
|
||||||
const instance = wrapper.instance();
|
const instance = wrapper.instance();
|
||||||
const updateAmount = sinon.spy(instance, 'updateAmount');
|
|
||||||
const updateGas = sinon.spy(instance, 'updateGas');
|
|
||||||
const validateAmount = sinon.spy(instance, 'validateAmount');
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
instance,
|
instance,
|
||||||
wrapper,
|
wrapper,
|
||||||
propsMethodSpies: {
|
propsMethodSpies: {
|
||||||
setMaxModeTo,
|
|
||||||
updateGasFeeError,
|
|
||||||
updateSendAmount,
|
updateSendAmount,
|
||||||
updateSendAmountError,
|
|
||||||
},
|
|
||||||
instanceSpies: {
|
|
||||||
updateAmount,
|
|
||||||
updateGas,
|
|
||||||
validateAmount,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,10 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
getGasTotal,
|
|
||||||
getPrimaryCurrency,
|
|
||||||
getSendToken,
|
|
||||||
getSendAmount,
|
|
||||||
getSendFromBalance,
|
|
||||||
getTokenBalance,
|
|
||||||
getSendMaxModeState,
|
|
||||||
sendAmountIsInError,
|
|
||||||
} from '../../../../selectors';
|
|
||||||
import { getAmountErrorObject, getGasFeeErrorObject } from '../../send.utils';
|
|
||||||
import {
|
|
||||||
updateSendErrors,
|
|
||||||
setMaxModeTo,
|
|
||||||
updateSendAmount,
|
updateSendAmount,
|
||||||
} from '../../../../ducks/send/send.duck';
|
getSendAmount,
|
||||||
import { getConversionRate } from '../../../../ducks/metamask/metamask';
|
sendAmountIsInError,
|
||||||
|
getSendAsset,
|
||||||
|
} from '../../../../ducks/send';
|
||||||
import SendAmountRow from './send-amount-row.component';
|
import SendAmountRow from './send-amount-row.component';
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(SendAmountRow);
|
export default connect(mapStateToProps, mapDispatchToProps)(SendAmountRow);
|
||||||
@ -23,26 +12,13 @@ export default connect(mapStateToProps, mapDispatchToProps)(SendAmountRow);
|
|||||||
function mapStateToProps(state) {
|
function mapStateToProps(state) {
|
||||||
return {
|
return {
|
||||||
amount: getSendAmount(state),
|
amount: getSendAmount(state),
|
||||||
balance: getSendFromBalance(state),
|
|
||||||
conversionRate: getConversionRate(state),
|
|
||||||
gasTotal: getGasTotal(state),
|
|
||||||
inError: sendAmountIsInError(state),
|
inError: sendAmountIsInError(state),
|
||||||
primaryCurrency: getPrimaryCurrency(state),
|
asset: getSendAsset(state),
|
||||||
sendToken: getSendToken(state),
|
|
||||||
tokenBalance: getTokenBalance(state),
|
|
||||||
maxModeOn: getSendMaxModeState(state),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
function mapDispatchToProps(dispatch) {
|
||||||
return {
|
return {
|
||||||
setMaxModeTo: (bool) => dispatch(setMaxModeTo(bool)),
|
|
||||||
updateSendAmount: (newAmount) => dispatch(updateSendAmount(newAmount)),
|
updateSendAmount: (newAmount) => dispatch(updateSendAmount(newAmount)),
|
||||||
updateGasFeeError: (amountDataObject) => {
|
|
||||||
dispatch(updateSendErrors(getGasFeeErrorObject(amountDataObject)));
|
|
||||||
},
|
|
||||||
updateSendAmountError: (amountDataObject) => {
|
|
||||||
dispatch(updateSendErrors(getAmountErrorObject(amountDataObject)));
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
|
|
||||||
import {
|
import { updateSendAmount } from '../../../../ducks/send';
|
||||||
updateSendErrors,
|
|
||||||
setMaxModeTo,
|
|
||||||
updateSendAmount,
|
|
||||||
} from '../../../../ducks/send/send.duck';
|
|
||||||
|
|
||||||
let mapDispatchToProps;
|
let mapDispatchToProps;
|
||||||
|
|
||||||
@ -15,24 +11,7 @@ jest.mock('react-redux', () => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('../../../../selectors/send.js', () => ({
|
jest.mock('../../../../ducks/send', () => ({
|
||||||
sendAmountIsInError: (s) => `mockInError:${s}`,
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('../../send.utils', () => ({
|
|
||||||
getAmountErrorObject: (mockDataObject) => ({
|
|
||||||
...mockDataObject,
|
|
||||||
mockChange: true,
|
|
||||||
}),
|
|
||||||
getGasFeeErrorObject: (mockDataObject) => ({
|
|
||||||
...mockDataObject,
|
|
||||||
mockGasFeeErrorChange: true,
|
|
||||||
}),
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('../../../../ducks/send/send.duck', () => ({
|
|
||||||
updateSendErrors: jest.fn(),
|
|
||||||
setMaxModeTo: jest.fn(),
|
|
||||||
updateSendAmount: jest.fn(),
|
updateSendAmount: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -48,15 +27,6 @@ describe('send-amount-row container', () => {
|
|||||||
mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy);
|
mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('setMaxModeTo()', () => {
|
|
||||||
it('should dispatch an action', () => {
|
|
||||||
mapDispatchToPropsObject.setMaxModeTo('mockBool');
|
|
||||||
expect(dispatchSpy.calledOnce).toStrictEqual(true);
|
|
||||||
expect(setMaxModeTo).toHaveBeenCalled();
|
|
||||||
expect(setMaxModeTo).toHaveBeenCalledWith('mockBool');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('updateSendAmount()', () => {
|
describe('updateSendAmount()', () => {
|
||||||
it('should dispatch an action', () => {
|
it('should dispatch an action', () => {
|
||||||
mapDispatchToPropsObject.updateSendAmount('mockAmount');
|
mapDispatchToPropsObject.updateSendAmount('mockAmount');
|
||||||
@ -65,29 +35,5 @@ describe('send-amount-row container', () => {
|
|||||||
expect(updateSendAmount).toHaveBeenCalledWith('mockAmount');
|
expect(updateSendAmount).toHaveBeenCalledWith('mockAmount');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('updateGasFeeError()', () => {
|
|
||||||
it('should dispatch an action', () => {
|
|
||||||
mapDispatchToPropsObject.updateGasFeeError({ some: 'data' });
|
|
||||||
expect(dispatchSpy.calledOnce).toStrictEqual(true);
|
|
||||||
expect(updateSendErrors).toHaveBeenCalled();
|
|
||||||
expect(updateSendErrors).toHaveBeenCalledWith({
|
|
||||||
some: 'data',
|
|
||||||
mockGasFeeErrorChange: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('updateSendAmountError()', () => {
|
|
||||||
it('should dispatch an action', () => {
|
|
||||||
mapDispatchToPropsObject.updateSendAmountError({ some: 'data' });
|
|
||||||
expect(dispatchSpy.calledOnce).toStrictEqual(true);
|
|
||||||
expect(updateSendErrors).toHaveBeenCalled();
|
|
||||||
expect(updateSendErrors).toHaveBeenCalledWith({
|
|
||||||
some: 'data',
|
|
||||||
mockChange: true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -5,6 +5,7 @@ import Identicon from '../../../../components/ui/identicon/identicon.component';
|
|||||||
import TokenBalance from '../../../../components/ui/token-balance';
|
import TokenBalance from '../../../../components/ui/token-balance';
|
||||||
import UserPreferencedCurrencyDisplay from '../../../../components/app/user-preferenced-currency-display';
|
import UserPreferencedCurrencyDisplay from '../../../../components/app/user-preferenced-currency-display';
|
||||||
import { ERC20, PRIMARY } from '../../../../helpers/constants/common';
|
import { ERC20, PRIMARY } from '../../../../helpers/constants/common';
|
||||||
|
import { ASSET_TYPES } from '../../../../ducks/send';
|
||||||
|
|
||||||
export default class SendAssetRow extends Component {
|
export default class SendAssetRow extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@ -18,13 +19,10 @@ export default class SendAssetRow extends Component {
|
|||||||
accounts: PropTypes.object.isRequired,
|
accounts: PropTypes.object.isRequired,
|
||||||
assetImages: PropTypes.object,
|
assetImages: PropTypes.object,
|
||||||
selectedAddress: PropTypes.string.isRequired,
|
selectedAddress: PropTypes.string.isRequired,
|
||||||
sendTokenAddress: PropTypes.string,
|
sendAssetAddress: PropTypes.string,
|
||||||
setSendToken: PropTypes.func.isRequired,
|
updateSendAsset: PropTypes.func.isRequired,
|
||||||
nativeCurrency: PropTypes.string,
|
nativeCurrency: PropTypes.string,
|
||||||
nativeCurrencyImage: PropTypes.string,
|
nativeCurrencyImage: PropTypes.string,
|
||||||
setUnsendableAssetError: PropTypes.func.isRequired,
|
|
||||||
updateSendErrors: PropTypes.func.isRequired,
|
|
||||||
updateTokenType: PropTypes.func.isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
@ -46,29 +44,7 @@ export default class SendAssetRow extends Component {
|
|||||||
|
|
||||||
closeDropdown = () => this.setState({ isShowingDropdown: false });
|
closeDropdown = () => this.setState({ isShowingDropdown: false });
|
||||||
|
|
||||||
clearUnsendableAssetError = () => {
|
selectToken = (type, token) => {
|
||||||
this.props.setUnsendableAssetError(false);
|
|
||||||
this.props.updateSendErrors({
|
|
||||||
unsendableAssetError: null,
|
|
||||||
gasLoadingError: null,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
selectToken = async (token) => {
|
|
||||||
if (token && token.isERC721 === undefined) {
|
|
||||||
const updatedToken = await this.props.updateTokenType(token.address);
|
|
||||||
if (updatedToken.isERC721) {
|
|
||||||
this.props.setUnsendableAssetError(true);
|
|
||||||
this.props.updateSendErrors({
|
|
||||||
unsendableAssetError: 'unsendableAssetError',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((token && token.isERC721 === false) || token === undefined) {
|
|
||||||
this.clearUnsendableAssetError();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState(
|
this.setState(
|
||||||
{
|
{
|
||||||
isShowingDropdown: false,
|
isShowingDropdown: false,
|
||||||
@ -84,7 +60,10 @@ export default class SendAssetRow extends Component {
|
|||||||
assetSelected: token ? ERC20 : this.props.nativeCurrency,
|
assetSelected: token ? ERC20 : this.props.nativeCurrency,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.props.setSendToken(token);
|
this.props.updateSendAsset({
|
||||||
|
type,
|
||||||
|
details: type === ASSET_TYPES.NATIVE ? null : token,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -105,9 +84,9 @@ export default class SendAssetRow extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderSendToken() {
|
renderSendToken() {
|
||||||
const { sendTokenAddress } = this.props;
|
const { sendAssetAddress } = this.props;
|
||||||
const token = this.props.tokens.find(
|
const token = this.props.tokens.find(
|
||||||
({ address }) => address === sendTokenAddress,
|
({ address }) => address === sendAssetAddress,
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -158,7 +137,7 @@ export default class SendAssetRow extends Component {
|
|||||||
? 'send-v2__asset-dropdown__asset'
|
? 'send-v2__asset-dropdown__asset'
|
||||||
: 'send-v2__asset-dropdown__single-asset'
|
: 'send-v2__asset-dropdown__single-asset'
|
||||||
}
|
}
|
||||||
onClick={() => this.selectToken()}
|
onClick={() => this.selectToken(ASSET_TYPES.NATIVE)}
|
||||||
>
|
>
|
||||||
<div className="send-v2__asset-dropdown__asset-icon">
|
<div className="send-v2__asset-dropdown__asset-icon">
|
||||||
<Identicon
|
<Identicon
|
||||||
@ -197,7 +176,7 @@ export default class SendAssetRow extends Component {
|
|||||||
<div
|
<div
|
||||||
key={address}
|
key={address}
|
||||||
className="send-v2__asset-dropdown__asset"
|
className="send-v2__asset-dropdown__asset"
|
||||||
onClick={() => this.selectToken(token)}
|
onClick={() => this.selectToken(ASSET_TYPES.TOKEN, token)}
|
||||||
>
|
>
|
||||||
<div className="send-v2__asset-dropdown__asset-icon">
|
<div className="send-v2__asset-dropdown__asset-icon">
|
||||||
<Identicon
|
<Identicon
|
||||||
|
@ -3,21 +3,16 @@ import { getNativeCurrency } from '../../../../ducks/metamask/metamask';
|
|||||||
import {
|
import {
|
||||||
getMetaMaskAccounts,
|
getMetaMaskAccounts,
|
||||||
getNativeCurrencyImage,
|
getNativeCurrencyImage,
|
||||||
getSendTokenAddress,
|
|
||||||
getAssetImages,
|
getAssetImages,
|
||||||
} from '../../../../selectors';
|
} from '../../../../selectors';
|
||||||
import { updateTokenType } from '../../../../store/actions';
|
import { updateSendAsset, getSendAssetAddress } from '../../../../ducks/send';
|
||||||
import {
|
|
||||||
updateSendErrors,
|
|
||||||
updateSendToken,
|
|
||||||
} from '../../../../ducks/send/send.duck';
|
|
||||||
import SendAssetRow from './send-asset-row.component';
|
import SendAssetRow from './send-asset-row.component';
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state) {
|
||||||
return {
|
return {
|
||||||
tokens: state.metamask.tokens,
|
tokens: state.metamask.tokens,
|
||||||
selectedAddress: state.metamask.selectedAddress,
|
selectedAddress: state.metamask.selectedAddress,
|
||||||
sendTokenAddress: getSendTokenAddress(state),
|
sendAssetAddress: getSendAssetAddress(state),
|
||||||
accounts: getMetaMaskAccounts(state),
|
accounts: getMetaMaskAccounts(state),
|
||||||
nativeCurrency: getNativeCurrency(state),
|
nativeCurrency: getNativeCurrency(state),
|
||||||
nativeCurrencyImage: getNativeCurrencyImage(state),
|
nativeCurrencyImage: getNativeCurrencyImage(state),
|
||||||
@ -27,11 +22,8 @@ function mapStateToProps(state) {
|
|||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
function mapDispatchToProps(dispatch) {
|
||||||
return {
|
return {
|
||||||
setSendToken: (token) => dispatch(updateSendToken(token)),
|
updateSendAsset: ({ type, details }) =>
|
||||||
updateTokenType: (tokenAddress) => dispatch(updateTokenType(tokenAddress)),
|
dispatch(updateSendAsset({ type, details })),
|
||||||
updateSendErrors: (error) => {
|
|
||||||
dispatch(updateSendErrors(error));
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,12 +18,8 @@ export default class SendContent extends Component {
|
|||||||
t: PropTypes.func,
|
t: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
|
||||||
unsendableAssetError: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
updateGas: PropTypes.func,
|
isAssetSendable: PropTypes.bool,
|
||||||
showAddToAddressBookModal: PropTypes.func,
|
showAddToAddressBookModal: PropTypes.func,
|
||||||
showHexData: PropTypes.bool,
|
showHexData: PropTypes.bool,
|
||||||
contact: PropTypes.object,
|
contact: PropTypes.object,
|
||||||
@ -35,11 +31,6 @@ export default class SendContent extends Component {
|
|||||||
noGasPrice: PropTypes.bool,
|
noGasPrice: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
updateGas = (updateData) => this.props.updateGas(updateData);
|
|
||||||
|
|
||||||
setUnsendableAssetError = (unsendableAssetError) =>
|
|
||||||
this.setState({ unsendableAssetError });
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
warning,
|
warning,
|
||||||
@ -47,9 +38,9 @@ export default class SendContent extends Component {
|
|||||||
gasIsExcessive,
|
gasIsExcessive,
|
||||||
isEthGasPrice,
|
isEthGasPrice,
|
||||||
noGasPrice,
|
noGasPrice,
|
||||||
|
isAssetSendable,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const { unsendableAssetError } = this.state;
|
|
||||||
let gasError;
|
let gasError;
|
||||||
if (gasIsExcessive) gasError = GAS_PRICE_EXCESSIVE_ERROR_KEY;
|
if (gasIsExcessive) gasError = GAS_PRICE_EXCESSIVE_ERROR_KEY;
|
||||||
else if (noGasPrice) gasError = GAS_PRICE_FETCH_FAILURE_ERROR_KEY;
|
else if (noGasPrice) gasError = GAS_PRICE_FETCH_FAILURE_ERROR_KEY;
|
||||||
@ -59,18 +50,15 @@ export default class SendContent extends Component {
|
|||||||
<div className="send-v2__form">
|
<div className="send-v2__form">
|
||||||
{gasError && this.renderError(gasError)}
|
{gasError && this.renderError(gasError)}
|
||||||
{isEthGasPrice && this.renderWarning(ETH_GAS_PRICE_FETCH_WARNING_KEY)}
|
{isEthGasPrice && this.renderWarning(ETH_GAS_PRICE_FETCH_WARNING_KEY)}
|
||||||
{unsendableAssetError && this.renderError(UNSENDABLE_ASSET_ERROR_KEY)}
|
{isAssetSendable === false &&
|
||||||
|
this.renderError(UNSENDABLE_ASSET_ERROR_KEY)}
|
||||||
{error && this.renderError(error)}
|
{error && this.renderError(error)}
|
||||||
{warning && this.renderWarning()}
|
{warning && this.renderWarning()}
|
||||||
{this.maybeRenderAddContact()}
|
{this.maybeRenderAddContact()}
|
||||||
<SendAssetRow
|
<SendAssetRow />
|
||||||
setUnsendableAssetError={this.setUnsendableAssetError}
|
<SendAmountRow />
|
||||||
/>
|
|
||||||
<SendAmountRow updateGas={this.updateGas} />
|
|
||||||
<SendGasRow />
|
<SendGasRow />
|
||||||
{this.props.showHexData && (
|
{this.props.showHexData && <SendHexDataRow />}
|
||||||
<SendHexDataRow updateGas={this.updateGas} />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</PageContainerContent>
|
</PageContainerContent>
|
||||||
);
|
);
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
getSendTo,
|
|
||||||
accountsWithSendEtherInfoSelector,
|
accountsWithSendEtherInfoSelector,
|
||||||
getAddressBookEntry,
|
getAddressBookEntry,
|
||||||
getIsEthGasPriceFetched,
|
getIsEthGasPriceFetched,
|
||||||
getNoGasPriceFetched,
|
getNoGasPriceFetched,
|
||||||
} from '../../../selectors';
|
} from '../../../selectors';
|
||||||
|
|
||||||
|
import { getIsAssetSendable, getSendTo } from '../../../ducks/send';
|
||||||
|
|
||||||
import * as actions from '../../../store/actions';
|
import * as actions from '../../../store/actions';
|
||||||
import SendContent from './send-content.component';
|
import SendContent from './send-content.component';
|
||||||
|
|
||||||
@ -14,15 +15,16 @@ function mapStateToProps(state) {
|
|||||||
const ownedAccounts = accountsWithSendEtherInfoSelector(state);
|
const ownedAccounts = accountsWithSendEtherInfoSelector(state);
|
||||||
const to = getSendTo(state);
|
const to = getSendTo(state);
|
||||||
return {
|
return {
|
||||||
|
isAssetSendable: getIsAssetSendable(state),
|
||||||
isOwnedAccount: Boolean(
|
isOwnedAccount: Boolean(
|
||||||
ownedAccounts.find(
|
ownedAccounts.find(
|
||||||
({ address }) => address.toLowerCase() === to.toLowerCase(),
|
({ address }) => address.toLowerCase() === to.toLowerCase(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
contact: getAddressBookEntry(state, to),
|
contact: getAddressBookEntry(state, to),
|
||||||
to,
|
|
||||||
isEthGasPrice: getIsEthGasPriceFetched(state),
|
isEthGasPrice: getIsEthGasPriceFetched(state),
|
||||||
noGasPrice: getNoGasPriceFetched(state),
|
noGasPrice: getNoGasPriceFetched(state),
|
||||||
|
to,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,31 +3,25 @@ import PropTypes from 'prop-types';
|
|||||||
import SendRowWrapper from '../send-row-wrapper';
|
import SendRowWrapper from '../send-row-wrapper';
|
||||||
import GasPriceButtonGroup from '../../../../components/app/gas-customization/gas-price-button-group';
|
import GasPriceButtonGroup from '../../../../components/app/gas-customization/gas-price-button-group';
|
||||||
import AdvancedGasInputs from '../../../../components/app/gas-customization/advanced-gas-inputs';
|
import AdvancedGasInputs from '../../../../components/app/gas-customization/advanced-gas-inputs';
|
||||||
|
import { GAS_INPUT_MODES } from '../../../../ducks/send';
|
||||||
import GasFeeDisplay from './gas-fee-display/gas-fee-display.component';
|
import GasFeeDisplay from './gas-fee-display/gas-fee-display.component';
|
||||||
|
|
||||||
export default class SendGasRow extends Component {
|
export default class SendGasRow extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
balance: PropTypes.string,
|
|
||||||
gasFeeError: PropTypes.bool,
|
gasFeeError: PropTypes.bool,
|
||||||
gasLoadingError: PropTypes.bool,
|
gasLoadingError: PropTypes.bool,
|
||||||
gasTotal: PropTypes.string,
|
gasTotal: PropTypes.string,
|
||||||
maxModeOn: PropTypes.bool,
|
|
||||||
showCustomizeGasModal: PropTypes.func,
|
showCustomizeGasModal: PropTypes.func,
|
||||||
sendToken: PropTypes.object,
|
updateGasPrice: PropTypes.func,
|
||||||
setAmountToMax: PropTypes.func,
|
updateGasLimit: PropTypes.func,
|
||||||
setGasPrice: PropTypes.func,
|
gasInputMode: PropTypes.oneOf(Object.values(GAS_INPUT_MODES)),
|
||||||
setGasLimit: PropTypes.func,
|
|
||||||
tokenBalance: PropTypes.string,
|
|
||||||
gasPriceButtonGroupProps: PropTypes.object,
|
gasPriceButtonGroupProps: PropTypes.object,
|
||||||
gasButtonGroupShown: PropTypes.bool,
|
|
||||||
advancedInlineGasShown: PropTypes.bool,
|
advancedInlineGasShown: PropTypes.bool,
|
||||||
resetGasButtons: PropTypes.func,
|
resetGasButtons: PropTypes.func,
|
||||||
gasPrice: PropTypes.string,
|
gasPrice: PropTypes.string,
|
||||||
gasLimit: PropTypes.string,
|
gasLimit: PropTypes.string,
|
||||||
insufficientBalance: PropTypes.bool,
|
insufficientBalance: PropTypes.bool,
|
||||||
isMainnet: PropTypes.bool,
|
minimumGasLimit: PropTypes.string,
|
||||||
isEthGasPrice: PropTypes.bool,
|
|
||||||
noGasPrice: PropTypes.bool,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
@ -37,19 +31,7 @@ export default class SendGasRow extends Component {
|
|||||||
|
|
||||||
renderAdvancedOptionsButton() {
|
renderAdvancedOptionsButton() {
|
||||||
const { trackEvent } = this.context;
|
const { trackEvent } = this.context;
|
||||||
const {
|
const { showCustomizeGasModal } = this.props;
|
||||||
showCustomizeGasModal,
|
|
||||||
isMainnet,
|
|
||||||
isEthGasPrice,
|
|
||||||
noGasPrice,
|
|
||||||
} = this.props;
|
|
||||||
// Tests should behave in same way as mainnet, but are using Localhost
|
|
||||||
if (!isMainnet && !process.env.IN_TEST) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (isEthGasPrice || noGasPrice) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="advanced-gas-options-btn"
|
className="advanced-gas-options-btn"
|
||||||
@ -66,44 +48,22 @@ export default class SendGasRow extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
setMaxAmount() {
|
|
||||||
const {
|
|
||||||
balance,
|
|
||||||
gasTotal,
|
|
||||||
sendToken,
|
|
||||||
setAmountToMax,
|
|
||||||
tokenBalance,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
setAmountToMax({
|
|
||||||
balance,
|
|
||||||
gasTotal,
|
|
||||||
sendToken,
|
|
||||||
tokenBalance,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
renderContent() {
|
renderContent() {
|
||||||
const {
|
const {
|
||||||
gasLoadingError,
|
gasLoadingError,
|
||||||
gasTotal,
|
gasTotal,
|
||||||
showCustomizeGasModal,
|
showCustomizeGasModal,
|
||||||
gasPriceButtonGroupProps,
|
gasPriceButtonGroupProps,
|
||||||
gasButtonGroupShown,
|
gasInputMode,
|
||||||
advancedInlineGasShown,
|
|
||||||
maxModeOn,
|
|
||||||
resetGasButtons,
|
resetGasButtons,
|
||||||
setGasPrice,
|
updateGasPrice,
|
||||||
setGasLimit,
|
updateGasLimit,
|
||||||
gasPrice,
|
gasPrice,
|
||||||
gasLimit,
|
gasLimit,
|
||||||
insufficientBalance,
|
insufficientBalance,
|
||||||
isMainnet,
|
minimumGasLimit,
|
||||||
isEthGasPrice,
|
|
||||||
noGasPrice,
|
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { trackEvent } = this.context;
|
const { trackEvent } = this.context;
|
||||||
const gasPriceFetchFailure = isEthGasPrice || noGasPrice;
|
|
||||||
|
|
||||||
const gasPriceButtonGroup = (
|
const gasPriceButtonGroup = (
|
||||||
<div>
|
<div>
|
||||||
@ -120,9 +80,6 @@ export default class SendGasRow extends Component {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
await gasPriceButtonGroupProps.handleGasPriceSelection(opts);
|
await gasPriceButtonGroupProps.handleGasPriceSelection(opts);
|
||||||
if (maxModeOn) {
|
|
||||||
this.setMaxAmount();
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -131,51 +88,38 @@ export default class SendGasRow extends Component {
|
|||||||
<GasFeeDisplay
|
<GasFeeDisplay
|
||||||
gasLoadingError={gasLoadingError}
|
gasLoadingError={gasLoadingError}
|
||||||
gasTotal={gasTotal}
|
gasTotal={gasTotal}
|
||||||
onReset={() => {
|
onReset={resetGasButtons}
|
||||||
resetGasButtons();
|
onClick={showCustomizeGasModal}
|
||||||
if (maxModeOn) {
|
|
||||||
this.setMaxAmount();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onClick={() => showCustomizeGasModal()}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
const advancedGasInputs = (
|
const advancedGasInputs = (
|
||||||
<div>
|
<div>
|
||||||
<AdvancedGasInputs
|
<AdvancedGasInputs
|
||||||
updateCustomGasPrice={(newGasPrice) =>
|
updateCustomGasPrice={updateGasPrice}
|
||||||
setGasPrice({ gasPrice: newGasPrice, gasLimit })
|
updateCustomGasLimit={updateGasLimit}
|
||||||
}
|
|
||||||
updateCustomGasLimit={(newGasLimit) =>
|
|
||||||
setGasLimit(newGasLimit, gasPrice)
|
|
||||||
}
|
|
||||||
customGasPrice={gasPrice}
|
customGasPrice={gasPrice}
|
||||||
customGasLimit={gasLimit}
|
customGasLimit={gasLimit}
|
||||||
insufficientBalance={insufficientBalance}
|
insufficientBalance={insufficientBalance}
|
||||||
|
minimumGasLimit={minimumGasLimit}
|
||||||
customPriceIsSafe
|
customPriceIsSafe
|
||||||
isSpeedUp={false}
|
isSpeedUp={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
// Tests should behave in same way as mainnet, but are using Localhost
|
// Tests should behave in same way as mainnet, but are using Localhost
|
||||||
if (
|
switch (gasInputMode) {
|
||||||
advancedInlineGasShown ||
|
case GAS_INPUT_MODES.BASIC:
|
||||||
(!isMainnet && !process.env.IN_TEST) ||
|
|
||||||
gasPriceFetchFailure
|
|
||||||
) {
|
|
||||||
return advancedGasInputs;
|
|
||||||
} else if (gasButtonGroupShown) {
|
|
||||||
return gasPriceButtonGroup;
|
return gasPriceButtonGroup;
|
||||||
}
|
case GAS_INPUT_MODES.INLINE:
|
||||||
|
return advancedGasInputs;
|
||||||
|
case GAS_INPUT_MODES.CUSTOM:
|
||||||
|
default:
|
||||||
return gasFeeDisplay;
|
return gasFeeDisplay;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const { gasFeeError, gasInputMode, advancedInlineGasShown } = this.props;
|
||||||
gasFeeError,
|
|
||||||
gasButtonGroupShown,
|
|
||||||
advancedInlineGasShown,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -186,7 +130,7 @@ export default class SendGasRow extends Component {
|
|||||||
>
|
>
|
||||||
{this.renderContent()}
|
{this.renderContent()}
|
||||||
</SendRowWrapper>
|
</SendRowWrapper>
|
||||||
{gasButtonGroupShown || advancedInlineGasShown ? (
|
{gasInputMode === GAS_INPUT_MODES.BASIC || advancedInlineGasShown ? (
|
||||||
<SendRowWrapper>{this.renderAdvancedOptionsButton()}</SendRowWrapper>
|
<SendRowWrapper>{this.renderAdvancedOptionsButton()}</SendRowWrapper>
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user