@ -74,6 +74,9 @@ workflows:
|
||||
- prep-build
|
||||
# - prep-docs
|
||||
- all-tests-pass
|
||||
- coveralls-upload:
|
||||
requires:
|
||||
- test-unit
|
||||
|
||||
jobs:
|
||||
create_release_pull_request:
|
||||
@ -204,7 +207,7 @@ jobs:
|
||||
at: .
|
||||
- run:
|
||||
name: test:e2e:firefox
|
||||
command: yarn build:test && yarn test:e2e:chrome
|
||||
command: yarn build:test && yarn test:e2e:firefox
|
||||
no_output_timeout: 20m
|
||||
- store_artifacts:
|
||||
path: test-artifacts
|
||||
@ -257,6 +260,11 @@ jobs:
|
||||
- run:
|
||||
name: test:coverage
|
||||
command: yarn test:coverage
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- .nyc_output
|
||||
- coverage
|
||||
test-mozilla-lint:
|
||||
docker:
|
||||
- image: circleci/node:10.16-browsers
|
||||
@ -303,6 +311,17 @@ jobs:
|
||||
name: All Tests Passed
|
||||
command: echo 'weew - everything passed!'
|
||||
|
||||
coveralls-upload:
|
||||
docker:
|
||||
- image: circleci/node:10.16-browsers
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Coveralls upload
|
||||
command: yarn test:coveralls-upload
|
||||
|
||||
create_github_release:
|
||||
docker:
|
||||
- image: circleci/node:8.15.1-browsers
|
||||
|
13
CHANGELOG.md
@ -2,6 +2,19 @@
|
||||
|
||||
## Current Develop Branch
|
||||
|
||||
## 7.1.0 Fri Aug 16 2019
|
||||
- [#7035](https://github.com/MetaMask/metamask-extension/pull/7035): Filter non-ERC-20 assets during mobile sync (#7035)
|
||||
- [#7021](https://github.com/MetaMask/metamask-extension/pull/7021): Using translated string for end of flow messaging (#7021)
|
||||
- [#7018](https://github.com/MetaMask/metamask-extension/pull/7018): Rename Contacts List settings tab to Contacts (#7018)
|
||||
- [#7013](https://github.com/MetaMask/metamask-extension/pull/7013): Connections settings tab (#7013)
|
||||
- [#6996](https://github.com/MetaMask/metamask-extension/pull/6996): Fetch & display received transactions (#6996)
|
||||
- [#6991](https://github.com/MetaMask/metamask-extension/pull/6991): Remove reload from Share Address button (#6991)
|
||||
- [#6978](https://github.com/MetaMask/metamask-extension/pull/6978): Address book fixes (#6978)
|
||||
- [#6944](https://github.com/MetaMask/metamask-extension/pull/6944): Show recipient alias in confirm header if exists (#6944)
|
||||
- [#6930](https://github.com/MetaMask/metamask-extension/pull/6930): Add support for eth_signTypedData_v4 (#6930)
|
||||
- [#7046](https://github.com/MetaMask/metamask-extension/pull/7046): Update Italian translation (#7046)
|
||||
- [#7047](https://github.com/MetaMask/metamask-extension/pull/7047): Add warning about reload on network change
|
||||
|
||||
## 7.0.1 Thu Aug 08 2019
|
||||
- [#6975](https://github.com/MetaMask/metamask-extension/pull/6975): Ensure seed phrase backup notification only shows up for new users
|
||||
|
||||
|
@ -1,10 +1,4 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "Režim súkromia"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "Webové stránky musia požiadať o prístup k zobrazeniu informácií o vašom účte."
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "Vystavte účty"
|
||||
},
|
||||
@ -20,12 +14,6 @@
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "Schválené údaje webových stránek byly úspěšně zrušeny."
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "Údaje o schválení"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "Vymažte schválené údaje webových stránek, aby všechny weby znovu požádaly o schválení."
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Jasné údaje o schválení"
|
||||
},
|
||||
|
@ -1,10 +1,4 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "Datenschutzmodus"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "Websites müssen Zugriff anfordern, um Ihre Kontoinformationen anzuzeigen."
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "Expose Konten"
|
||||
},
|
||||
@ -20,12 +14,6 @@
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "Genehmigte Website-Daten wurden erfolgreich gelöscht."
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "Genehmigungsdaten"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "Löschen Sie die genehmigten Website-Daten, damit alle Websites erneut eine Genehmigung anfordern müssen."
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Genehmigungsdaten löschen"
|
||||
},
|
||||
|
@ -1,21 +1,9 @@
|
||||
{
|
||||
"shareAddress": {
|
||||
"message": "Share Address"
|
||||
"showIncomingTransactions": {
|
||||
"message": "Show Incoming Transactions"
|
||||
},
|
||||
"shareAddressToConnect": {
|
||||
"message": "Share your address to connect to $1?"
|
||||
},
|
||||
"shareAddressInfo": {
|
||||
"message": "Sharing your address with $1 will allow you to interact with this dapp. This permission is to protect your privacy by default."
|
||||
},
|
||||
"privacyModeDefault": {
|
||||
"message": "Privacy Mode is now enabled by default"
|
||||
},
|
||||
"privacyMode": {
|
||||
"message": "Privacy Mode"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "Websites must request access to view your account information."
|
||||
"showIncomingTransactionsDescription": {
|
||||
"message": "Select this to use Etherscan to show incoming transactions in the transactions list"
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "Expose Accounts"
|
||||
@ -32,20 +20,35 @@
|
||||
"confirmClear": {
|
||||
"message": "Are you sure you want to clear approved websites?"
|
||||
},
|
||||
"connections": {
|
||||
"message": "Connections"
|
||||
},
|
||||
"connectionsSettingsDescription": {
|
||||
"message": "Sites allowed to read your accounts"
|
||||
},
|
||||
"addSite": {
|
||||
"message": "Add Site"
|
||||
},
|
||||
"addSiteDescription": {
|
||||
"message": "Manually add a site to allow it access to your accounts, useful for older dapps"
|
||||
},
|
||||
"connected": {
|
||||
"message": "Connected"
|
||||
},
|
||||
"connectedDescription": {
|
||||
"message": "The list of sites allowed access to your addresses"
|
||||
},
|
||||
"privacyModeDefault": {
|
||||
"message": "Privacy Mode is now enabled by default"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "Contract Interaction"
|
||||
},
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "Approved website data cleared successfully."
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "Privacy Data"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "Clear privacy data so all websites must request access to view account information again."
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Clear Privacy Data"
|
||||
"message": "Remove all sites"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Reject"
|
||||
@ -399,10 +402,10 @@
|
||||
"connectToTrezor": {
|
||||
"message": "Connect to Trezor"
|
||||
},
|
||||
"contactList": {
|
||||
"message": "Contact List"
|
||||
"contacts": {
|
||||
"message": "Contacts"
|
||||
},
|
||||
"contactListDescription": {
|
||||
"contactsSettingsDescription": {
|
||||
"message": "Add, edit, remove, and manage your contacts"
|
||||
},
|
||||
"continue": {
|
||||
@ -620,7 +623,7 @@
|
||||
"message": "If you ever have questions or see something fishy, email support@metamask.io."
|
||||
},
|
||||
"endOfFlowMessage8": {
|
||||
"message": "MetaMask cannot recover your seedphrase. Learn more."
|
||||
"message": "MetaMask cannot recover your seedphrase."
|
||||
},
|
||||
"endOfFlowMessage9": {
|
||||
"message": "Learn more."
|
||||
|
@ -1,10 +1,4 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "Modo privado"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "Los sitios web deben solicitar acceso para ver la información de su cuenta."
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "Exponer cuentas"
|
||||
},
|
||||
@ -20,12 +14,6 @@
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "Los datos aprobados del sitio web se borraron con éxito."
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "Datos de aprobación"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "Borrar la información privada de modo que todos los sitios deban volver a requerir acceso para acceder a los datos de la cuenta."
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Borrar datos de aprobación"
|
||||
},
|
||||
|
@ -1,10 +1,4 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "Les sites Web doivent demander un accès pour afficher les informations de votre compte."
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "Les sites Web doivent demander un accès pour afficher les informations de votre compte."
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "Exposer les comptes"
|
||||
},
|
||||
@ -20,12 +14,6 @@
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "Les données de site Web approuvées ont été supprimées."
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "Données d'approbation"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "Effacer les données de site Web approuvées afin que tous les sites doivent à nouveau demander l'approbation."
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Effacer les données d'approbation"
|
||||
},
|
||||
|
@ -1,10 +1,4 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "गोपनीयता मोड"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "वेबसाइटों को आपकी खाता जानकारी देखने के लिए पहुंच का अनुरोध करना होगा।"
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "खातों का पर्दाफाश करें"
|
||||
},
|
||||
@ -20,12 +14,6 @@
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "स्वीकृत वेबसाइट डेटा सफलतापूर्वक मंजूरी दे दी।"
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "स्वीकृति डेटा"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "अनुमोदित वेबसाइट डेटा साफ़ करें ताकि सभी साइटों को फिर से अनुमोदन का अनुरोध करना होगा।"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "अनुमोदन डेटा साफ़ करें"
|
||||
},
|
||||
|
@ -1,10 +1,4 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "Mòd Privacy"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "Sou sit entènèt yo dwe mande aksè pou wè enfòmasyon kont ou."
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "Ekspoze Kont"
|
||||
},
|
||||
@ -20,12 +14,6 @@
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "Done sou sit wèb apwouve yo te klarifye avèk siksè."
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "Done sou vi prive"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "Done sou vi prive klè pou tout sit entènèt yo dwe mande aksè pou wè enfòmasyon kont ankò."
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Klè Done sou vi prive"
|
||||
},
|
||||
|
@ -1,10 +1,4 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "Modalità privacy"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "I siti Web devono richiedere l'accesso per visualizzare le informazioni del tuo account."
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "Esponi Accounts"
|
||||
},
|
||||
@ -20,20 +14,35 @@
|
||||
"confirmClear": {
|
||||
"message": "Sei sicuro di voler cancellare i siti Web approvati?"
|
||||
},
|
||||
"connections": {
|
||||
"message": "Connessioni"
|
||||
},
|
||||
"connectionsSettingsDescription": {
|
||||
"message": "Siti autorizzati ad accedere ai tuoi accounts"
|
||||
},
|
||||
"addSite": {
|
||||
"message": "Aggiungi Sito"
|
||||
},
|
||||
"addSiteDescription": {
|
||||
"message": "Aggiungi un sito autorizzato ad accedere ai tuoi accounts, utile per dapps obsolete"
|
||||
},
|
||||
"connected": {
|
||||
"message": "Connesso"
|
||||
},
|
||||
"connectedDescription": {
|
||||
"message": "La lista di siti web autorizzati ad accedere ai tuoi indirizzi"
|
||||
},
|
||||
"privacyModeDefault": {
|
||||
"message": "La Modalità Privacy è attiva per impostazione predefinita"
|
||||
},
|
||||
"contractInteraction": {
|
||||
"message": "Interazione Contratto"
|
||||
},
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "Dati del sito Web approvati cancellati correttamente."
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "Dati di approvazione"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "Cancella i dati del sito web approvati, quindi tutti i siti devono richiedere nuovamente l'approvazione."
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Cancella i dati di approvazione"
|
||||
"message": "Rimuovi tutti i siti"
|
||||
},
|
||||
"reject": {
|
||||
"message": "Annulla"
|
||||
@ -80,12 +89,21 @@
|
||||
"activityLog": {
|
||||
"message": "log attività"
|
||||
},
|
||||
"add": {
|
||||
"message": "Aggiungi"
|
||||
},
|
||||
"address": {
|
||||
"message": "Indirizzo"
|
||||
},
|
||||
"addNetwork": {
|
||||
"message": "Aggiungi Rete"
|
||||
},
|
||||
"addRecipient": {
|
||||
"message": "Aggiungi Destinatario"
|
||||
},
|
||||
"addressBook": {
|
||||
"message": "Rubrica"
|
||||
},
|
||||
"advanced": {
|
||||
"message": "Avanzate"
|
||||
},
|
||||
@ -98,6 +116,18 @@
|
||||
"addCustomToken": {
|
||||
"message": "Aggiungi un token personalizzato"
|
||||
},
|
||||
"addToAddressBook": {
|
||||
"message": "Aggiungi alla rubrica"
|
||||
},
|
||||
"addToAddressBookModalPlaceholder": {
|
||||
"message": "es. John D."
|
||||
},
|
||||
"addAlias": {
|
||||
"message": "Aggiungi alias"
|
||||
},
|
||||
"addEthAddress": {
|
||||
"message": "Aggiungi un indirizzo Ethereum"
|
||||
},
|
||||
"addToken": {
|
||||
"message": "Aggiungi Token"
|
||||
},
|
||||
@ -172,6 +202,18 @@
|
||||
"back": {
|
||||
"message": "Indietro"
|
||||
},
|
||||
"backToAll": {
|
||||
"message": "Torna a Tutti"
|
||||
},
|
||||
"backupApprovalNotice": {
|
||||
"message": "Per mantenere il portafoglio e i tuoi fondi sicuri fai il backup del tuo codice di recupero segreto."
|
||||
},
|
||||
"backupApprovalInfo": {
|
||||
"message": "Questo codice segreto è necessario per recuperare il portafoglio in caso di smarrimento del dispositivo, si dimentichi la password, di necessità di reinstallare MetaMask, o volontà di accedere al portafoglio da un altro dispositivo."
|
||||
},
|
||||
"backupNow": {
|
||||
"message": "Backup"
|
||||
},
|
||||
"balance": {
|
||||
"message": "Bilancio:"
|
||||
},
|
||||
@ -237,9 +279,15 @@
|
||||
"bytes": {
|
||||
"message": "Bytes"
|
||||
},
|
||||
"off": {
|
||||
"message": "Off"
|
||||
},
|
||||
"ok": {
|
||||
"message": "Ok"
|
||||
},
|
||||
"on": {
|
||||
"message": "On"
|
||||
},
|
||||
"optionalBlockExplorerUrl": {
|
||||
"message": "URL del Block Explorer (opzionale)"
|
||||
},
|
||||
@ -348,6 +396,12 @@
|
||||
"connectToTrezor": {
|
||||
"message": "Connettersi al Trezor"
|
||||
},
|
||||
"contacts": {
|
||||
"message": "Contatti"
|
||||
},
|
||||
"contactsSettingsDescription": {
|
||||
"message": "Aggiungi, modifica, rimuovi e gestisci i tuoi contatti"
|
||||
},
|
||||
"continue": {
|
||||
"message": "Continua"
|
||||
},
|
||||
@ -454,6 +508,12 @@
|
||||
"defaultNetwork": {
|
||||
"message": "La rete predefinita per transazioni in Ether è la Rete Ethereum Principale."
|
||||
},
|
||||
"delete": {
|
||||
"message": "Elimina"
|
||||
},
|
||||
"deleteAccount": {
|
||||
"message": "Elimina Account"
|
||||
},
|
||||
"denExplainer": {
|
||||
"message": "Il DEN è il tuo archivio crittato con password dentro MetaMask."
|
||||
},
|
||||
@ -493,6 +553,9 @@
|
||||
"directDepositEtherExplainer": {
|
||||
"message": "Se possiedi già degli Ether, questa è la via più veloce per aggiungere Ether al tuo portafoglio con un deposito diretto."
|
||||
},
|
||||
"dismiss": {
|
||||
"message": "Scarta"
|
||||
},
|
||||
"done": {
|
||||
"message": "Finito"
|
||||
},
|
||||
@ -520,6 +583,9 @@
|
||||
"editAccountName": {
|
||||
"message": "Modifica Nome Account"
|
||||
},
|
||||
"editContact": {
|
||||
"message": "Modifica Contatto"
|
||||
},
|
||||
"editingTransaction": {
|
||||
"message": "Modifica la transazione"
|
||||
},
|
||||
@ -551,11 +617,26 @@
|
||||
"message": "Se hai delle domande o vedi delle attività sospette, manda una mail a support@metamask.io."
|
||||
},
|
||||
"endOfFlowMessage8": {
|
||||
"message": "MetaMask non può recuperare la tua frase seed. Impara di più."
|
||||
"message": "MetaMask non può recuperare la tua frase seed."
|
||||
},
|
||||
"endOfFlowMessage9": {
|
||||
"message": "Scopri di più."
|
||||
},
|
||||
"endOfFlowMessage10": {
|
||||
"message": "Tutto fatto."
|
||||
},
|
||||
"ensNameNotFound": {
|
||||
"message": "Nome ENS non trovato"
|
||||
},
|
||||
"ensRegistrationError": {
|
||||
"message": "Errore nella registrazione del nome ENS"
|
||||
},
|
||||
"ensNotFoundOnCurrentNetwork": {
|
||||
"message": "Nome ENS non trovato nella rete corrente. Prova a selezionare la Rete Ethereum Principale"
|
||||
},
|
||||
"enterAnAlias": {
|
||||
"message": "Inserisci un alias"
|
||||
},
|
||||
"enterPassword": {
|
||||
"message": "Inserisci password"
|
||||
},
|
||||
@ -568,6 +649,9 @@
|
||||
"eth": {
|
||||
"message": "ETH"
|
||||
},
|
||||
"ethereumPublicAddress": {
|
||||
"message": "Indirizzo Pubblico Ethereum"
|
||||
},
|
||||
"etherscanView": {
|
||||
"message": "Vedi account su Etherscan"
|
||||
},
|
||||
@ -656,7 +740,7 @@
|
||||
"message": "Generando la frase seed..."
|
||||
},
|
||||
"gasPrice": {
|
||||
"message": "Prezzo del Gas (GWEI)"
|
||||
"message": "Prezzo Gas (GWEI)"
|
||||
},
|
||||
"gasPriceExtremelyLow": {
|
||||
"message": "Prezzo del gas estremamente basso"
|
||||
@ -878,6 +962,9 @@
|
||||
"loadingTokens": {
|
||||
"message": "Caricamento Tokens..."
|
||||
},
|
||||
"loadMore": {
|
||||
"message": "Carica altro"
|
||||
},
|
||||
"localhost": {
|
||||
"message": "Localhost 8545"
|
||||
},
|
||||
@ -899,6 +986,9 @@
|
||||
"memorizePhrase": {
|
||||
"message": "Memorizza questa frase."
|
||||
},
|
||||
"memo": {
|
||||
"message": "Promemoria"
|
||||
},
|
||||
"menu": {
|
||||
"message": "Menu"
|
||||
},
|
||||
@ -930,7 +1020,13 @@
|
||||
"message": "Per favore inserisci la password per confermare che sei te!"
|
||||
},
|
||||
"myAccounts": {
|
||||
"message": "Miei Account"
|
||||
"message": "I Miei Accounts"
|
||||
},
|
||||
"myWalletAccounts": {
|
||||
"message": "I Miei Accounts"
|
||||
},
|
||||
"myWalletAccountsDescription": {
|
||||
"message": "Tutti i tuoi account MetaMask saranno automaticamente aggiunti in questa sezione."
|
||||
},
|
||||
"mustSelectOne": {
|
||||
"message": "Devi selezionare almeno un token."
|
||||
@ -964,10 +1060,16 @@
|
||||
"newAccount": {
|
||||
"message": "Nuovo Account"
|
||||
},
|
||||
"newAccountDetectedDialogMessage": {
|
||||
"message": "Rilevato nuovo indirizzo! Clica qua per aggiungerlo alla rubrica."
|
||||
},
|
||||
"newAccountNumberName": {
|
||||
"message": "Account $1",
|
||||
"description": "Nome predefinito per il prossimo account da creare nella schermata di creazione account"
|
||||
},
|
||||
"newContact": {
|
||||
"message": "Nuovo Contatto"
|
||||
},
|
||||
"newContract": {
|
||||
"message": "Nuovo Contratto"
|
||||
},
|
||||
@ -1178,9 +1280,15 @@
|
||||
"receive": {
|
||||
"message": "Ricevi"
|
||||
},
|
||||
"recents": {
|
||||
"message": "Recenti"
|
||||
},
|
||||
"recipientAddress": {
|
||||
"message": "Indirizzo Destinatario"
|
||||
},
|
||||
"recipientAddressPlaceholder": {
|
||||
"message": "Ricerca, indirizzo pubblico (0x), o ENS"
|
||||
},
|
||||
"refundAddress": {
|
||||
"message": "Indirizzo di Rimborso"
|
||||
},
|
||||
@ -1205,6 +1313,15 @@
|
||||
"resetAccountDescription": {
|
||||
"message": "Ripristinare il tuo account cancellerà lo storico delle transazioni."
|
||||
},
|
||||
"deleteNetwork": {
|
||||
"message": "Elimina Rete?"
|
||||
},
|
||||
"deleteNetworkDescription": {
|
||||
"message": "Sei sicuro di voler eliminare questa rete?"
|
||||
},
|
||||
"remindMeLater": {
|
||||
"message": "Più tardi"
|
||||
},
|
||||
"restoreFromSeed": {
|
||||
"message": "Ripristina da una frase seed"
|
||||
},
|
||||
@ -1454,7 +1571,7 @@
|
||||
"message": "ci può essere solo uno spazio tra le parole"
|
||||
},
|
||||
"speedUp": {
|
||||
"message": "velocizza"
|
||||
"message": "Velocizza"
|
||||
},
|
||||
"speedUpTitle": {
|
||||
"message": "Velocizza Transazione"
|
||||
@ -1649,6 +1766,9 @@
|
||||
"transfer": {
|
||||
"message": "Trasferisci"
|
||||
},
|
||||
"transferBetweenAccounts": {
|
||||
"message": "Trasferisci tra i miei accounts"
|
||||
},
|
||||
"transferFrom": {
|
||||
"message": "Trasferisci Da"
|
||||
},
|
||||
@ -1729,6 +1849,9 @@
|
||||
"useOldUI": {
|
||||
"message": "Use la vecchia UI"
|
||||
},
|
||||
"userName": {
|
||||
"message": "Username"
|
||||
},
|
||||
"validFileImport": {
|
||||
"message": "Devi selezionare un file valido da importare."
|
||||
},
|
||||
@ -1738,6 +1861,12 @@
|
||||
"viewAccount": {
|
||||
"message": "Vedi Account"
|
||||
},
|
||||
"viewinExplorer": {
|
||||
"message": "Vedi in Explorer"
|
||||
},
|
||||
"viewContact": {
|
||||
"message": "Visualizza Contatto"
|
||||
},
|
||||
"viewOnCustomBlockExplorer": {
|
||||
"message": "Vedi su $1"
|
||||
},
|
||||
|
@ -1,10 +1,4 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "プライバシーモード"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "ウェブサイトはあなたのアカウント情報を閲覧するためのアクセスを要求する必要があります。"
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "アカウントを公開する"
|
||||
},
|
||||
@ -20,12 +14,6 @@
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "承認されたウェブサイトデータが正常に消去されました。"
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "承認データ"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "承認されたウェブサイトのデータをクリアすると、すべてのサイトで承認を再度要求する必要があります"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "承認データのクリア"
|
||||
},
|
||||
|
@ -1,10 +1,4 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "개인 정보 보호 모드"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "웹 사이트는 계정 정보를 볼 수있는 액세스 권한을 요청해야합니다."
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "계정 노출"
|
||||
},
|
||||
@ -20,12 +14,6 @@
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "승인 된 웹 사이트 데이터가 성공적으로 삭제되었습니다."
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "승인 데이터"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "승인 된 웹 사이트 데이터를 삭제하여 모든 사이트에서 승인을 다시 요청해야합니다."
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "승인 데이터 삭제"
|
||||
},
|
||||
|
@ -1,10 +1,4 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "Privacy-modus"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "Websites moeten toegang vragen om uw accountgegevens te bekijken."
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "Expose Accounts"
|
||||
},
|
||||
@ -20,12 +14,6 @@
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "Goedgekeurde websitegegevens zijn met succes gewist."
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "Goedkeuringsgegevens"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "Goedgekeurde websitegegevens wissen zodat alle sites opnieuw goedkeuring moeten aanvragen."
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Gegevens over goedkeuring wissen"
|
||||
},
|
||||
|
@ -1,10 +1,4 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "Mode ng Privacy"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "Dapat humiling ng access ang mga website upang tingnan ang impormasyon ng iyong account."
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "Ilantad ang Mga Account"
|
||||
},
|
||||
@ -20,12 +14,6 @@
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "Matagumpay na na-clear ang data ng aprubadong website."
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "Data ng Pag-apruba"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "I-clear ang naaprubahang data ng website upang ang lahat ng site ay dapat humiling muli ng pag-apruba"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Tanggalin ang data ng pag-apruba"
|
||||
},
|
||||
|
@ -1,10 +1,4 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "Modo de privacidade"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "Os sites devem solicitar acesso para visualizar as informações da sua conta."
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "Expor contas"
|
||||
},
|
||||
@ -20,12 +14,6 @@
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "Dados aprovados do website foram limpos com sucesso."
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "Dados de aprovação"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "Limpe os dados aprovados do website para que todos os sites solicitem aprovação novamente."
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Limpar dados de aprovação"
|
||||
},
|
||||
|
@ -1,10 +1,4 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "Режим конфиденциальности"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "Веб-сайты должны запрашивать доступ для просмотра информации об учетной записи."
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "Открыть счета"
|
||||
},
|
||||
@ -20,12 +14,6 @@
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "Утвержденные данные веб-сайта успешно удалены."
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "Данные об утверждении"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "Очистите утвержденные данные веб-сайта, чтобы все сайты снова запросили подтверждение."
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Четкие данные об утверждении"
|
||||
},
|
||||
|
@ -1,10 +1,4 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "Režim súkromia"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "Webové stránky musia požiadať o prístup k zobrazeniu informácií o vašom účte."
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "Vystavte účty"
|
||||
},
|
||||
@ -20,12 +14,6 @@
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "Schválené údaje webových stránek byly úspěšně zrušeny."
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "Údaje o schválení"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "Vymažte schválené údaje webových stránek, aby všechny weby znovu požádaly o schválení."
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Jasné údaje o schválení"
|
||||
},
|
||||
|
@ -1,10 +1,4 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "Zasebnostni način"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "Spletne strani morajo zahtevati dovoljenje za ogled podatkov o vašem računu."
|
||||
},
|
||||
"privacyNotice": {
|
||||
"message": "Obvestilo o zasebnosti"
|
||||
},
|
||||
@ -26,12 +20,6 @@
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "Odobrene spletne strani uspešno počiščene."
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "Podatki o odobritvi"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "Počistite seznam odobrenih spletnih strani, tako da bodo morale ponovno zahtevati odobritev."
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Počisti podatke o odobritvi"
|
||||
},
|
||||
|
@ -1,10 +1,4 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "โหมดความเป็นส่วนตัว"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "เว็บไซต์ต้องขอเข้าถึงเพื่อดูข้อมูลบัญชีของคุณ"
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "เปิดเผยบัญชี"
|
||||
},
|
||||
@ -20,12 +14,6 @@
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "อนุมัติข้อมูลเว็บไซต์ที่ได้รับอนุมัติแล้ว"
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "ข้อมูลการอนุมัติ"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "ล้างข้อมูลเว็บไซต์ที่ได้รับการอนุมัติเพื่อให้ทุกไซต์ต้องขออนุมัติอีกครั้ง"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "ล้างข้อมูลการอนุมัติ"
|
||||
},
|
||||
|
@ -1,10 +1,4 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "தனியுரிமை முறை"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "உங்கள் கணக்குத் தகவலை பார்வையிட வலைத்தளங்கள் அணுகலைக் கோர வேண்டும்."
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "கணக்குகளை அம்பலப்படுத்துங்கள்"
|
||||
},
|
||||
@ -20,12 +14,6 @@
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "அங்கீகரிக்கப்பட்ட வலைத்தள தரவு வெற்றிகரமாக அழிக்கப்பட்டது."
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "ஒப்புதல் தரவு"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "அங்கீகரிக்கப்பட்ட வலைத்தள தரவை அழிக்கவும், அனைத்து தளங்களும் ஒப்புதல் மீண்டும் கோர வேண்டும்."
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "ஒப்புதல் தரவை அழி"
|
||||
},
|
||||
|
@ -1,10 +1,4 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "Gizlilik modu"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "Web siteleri, hesap bilgilerinizi görmek için erişim istemek zorundadır."
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "Hesapları Açığa Çıkar"
|
||||
},
|
||||
@ -20,12 +14,6 @@
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "Onaylanan web sitesi verileri başarıyla temizlendi."
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "Onay Verileri"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "Onaylanan web sitesi verilerini temizle, tüm sitelerin tekrar onay isteğinde bulunması gerekir."
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Onay verilerini temizle"
|
||||
},
|
||||
|
@ -1,10 +1,4 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "Chế độ riêng tư"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "Trang web phải yêu cầu quyền truy cập để xem thông tin tài khoản của bạn."
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "Hiển thị tài khoản"
|
||||
},
|
||||
@ -20,12 +14,6 @@
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "Đã xóa thành công dữ liệu trang web được phê duyệt."
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "Dữ liệu phê duyệt"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "Xóa dữ liệu trang web được phê duyệt để tất cả các trang web phải yêu cầu phê duyệt lại."
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "Xóa dữ liệu phê duyệt"
|
||||
},
|
||||
|
@ -1,10 +1,4 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "隐私模式"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "网站必须请求访问权限才能查看您的帐户信息。"
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "公开账户"
|
||||
},
|
||||
@ -20,12 +14,6 @@
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "已批准的网站数据已成功清除。"
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "审批数据"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "清除已批准的网站数据,以便所有网站都必须再次申请"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "清除批准数据"
|
||||
},
|
||||
|
@ -1,10 +1,4 @@
|
||||
{
|
||||
"privacyMode": {
|
||||
"message": "隱私模式"
|
||||
},
|
||||
"privacyModeDescription": {
|
||||
"message": "網站必須請求訪問權限才能查看您的帳戶資訊"
|
||||
},
|
||||
"exposeAccounts": {
|
||||
"message": "公開賬戶"
|
||||
},
|
||||
@ -23,12 +17,6 @@
|
||||
"clearApprovalDataSuccess": {
|
||||
"message": "已批准的網站紀錄已成功清除。"
|
||||
},
|
||||
"approvalData": {
|
||||
"message": "審核紀錄"
|
||||
},
|
||||
"approvalDataDescription": {
|
||||
"message": "清除之前已批准過的網站審核紀錄,所有網站都必須再次申請"
|
||||
},
|
||||
"clearApprovalData": {
|
||||
"message": "清除批准數據"
|
||||
},
|
||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 692 B After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 93 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 17 KiB |
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "__MSG_appName__",
|
||||
"short_name": "__MSG_appName__",
|
||||
"version": "7.0.1",
|
||||
"version": "7.1.0",
|
||||
"manifest_version": 2,
|
||||
"author": "https://metamask.io",
|
||||
"description": "__MSG_appDescription__",
|
||||
|
@ -142,7 +142,6 @@ setupMetamaskMeshMetrics()
|
||||
* @property {Object} infuraNetworkStatus - An object of infura network status checks.
|
||||
* @property {Block[]} recentBlocks - An array of recent blocks, used to calculate an effective but cheap gas price.
|
||||
* @property {Array} shapeShiftTxList - An array of objects describing shapeshift exchange attempts.
|
||||
* @property {Array} lostAccounts - TODO: Remove this feature. A leftover from the version-3 migration where our seed-phrase library changed to fix a bug where some accounts were mis-generated, but we recovered the old accounts as "lost" instead of losing them.
|
||||
* @property {boolean} forgottenPassword - Returns true if the user has initiated the password recovery screen, is recovering from seed phrase.
|
||||
*/
|
||||
|
||||
@ -410,7 +409,7 @@ function setupController (initState, initLangCode) {
|
||||
controller.messageManager.on('updateBadge', updateBadge)
|
||||
controller.personalMessageManager.on('updateBadge', updateBadge)
|
||||
controller.typedMessageManager.on('updateBadge', updateBadge)
|
||||
controller.providerApprovalController.store.on('update', updateBadge)
|
||||
controller.providerApprovalController.memStore.on('update', updateBadge)
|
||||
|
||||
/**
|
||||
* Updates the Web Extension's "badge" number, on the little fox in the toolbar.
|
||||
@ -422,7 +421,7 @@ function setupController (initState, initLangCode) {
|
||||
const unapprovedMsgCount = controller.messageManager.unapprovedMsgCount
|
||||
const unapprovedPersonalMsgs = controller.personalMessageManager.unapprovedPersonalMsgCount
|
||||
const unapprovedTypedMsgs = controller.typedMessageManager.unapprovedTypedMessagesCount
|
||||
const pendingProviderRequests = controller.providerApprovalController.store.getState().providerRequests.length
|
||||
const pendingProviderRequests = controller.providerApprovalController.memStore.getState().providerRequests.length
|
||||
const count = unapprovedTxCount + unapprovedMsgCount + unapprovedPersonalMsgs + unapprovedTypedMsgs + pendingProviderRequests
|
||||
if (count) {
|
||||
label = String(count)
|
||||
|
@ -114,7 +114,6 @@ function forwardTrafficBetweenMuxers (channelName, muxA, muxB) {
|
||||
|
||||
async function setupPublicApi (outStream) {
|
||||
const api = {
|
||||
forceReloadSite: (cb) => cb(null, forceReloadSite()),
|
||||
getSiteMetadata: (cb) => cb(null, getSiteMetadata()),
|
||||
}
|
||||
const dnode = Dnode(api)
|
||||
@ -307,10 +306,3 @@ async function domIsReady () {
|
||||
// wait for load
|
||||
await new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve, { once: true }))
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads the site
|
||||
*/
|
||||
function forceReloadSite () {
|
||||
window.location.reload()
|
||||
}
|
||||
|
284
app/scripts/controllers/incoming-transactions.js
Normal file
@ -0,0 +1,284 @@
|
||||
const ObservableStore = require('obs-store')
|
||||
const log = require('loglevel')
|
||||
const BN = require('bn.js')
|
||||
const createId = require('../lib/random-id')
|
||||
const { bnToHex, fetchWithTimeout } = require('../lib/util')
|
||||
const {
|
||||
MAINNET_CODE,
|
||||
ROPSTEN_CODE,
|
||||
RINKEYBY_CODE,
|
||||
KOVAN_CODE,
|
||||
ROPSTEN,
|
||||
RINKEBY,
|
||||
KOVAN,
|
||||
MAINNET,
|
||||
} = require('./network/enums')
|
||||
const networkTypeToIdMap = {
|
||||
[ROPSTEN]: String(ROPSTEN_CODE),
|
||||
[RINKEBY]: String(RINKEYBY_CODE),
|
||||
[KOVAN]: String(KOVAN_CODE),
|
||||
[MAINNET]: String(MAINNET_CODE),
|
||||
}
|
||||
const fetch = fetchWithTimeout({
|
||||
timeout: 30000,
|
||||
})
|
||||
|
||||
class IncomingTransactionsController {
|
||||
|
||||
constructor (opts = {}) {
|
||||
const {
|
||||
blockTracker,
|
||||
networkController,
|
||||
preferencesController,
|
||||
} = opts
|
||||
this.blockTracker = blockTracker
|
||||
this.networkController = networkController
|
||||
this.preferencesController = preferencesController
|
||||
this.getCurrentNetwork = () => networkController.getProviderConfig().type
|
||||
|
||||
this._onLatestBlock = async (newBlockNumberHex) => {
|
||||
const selectedAddress = this.preferencesController.getSelectedAddress()
|
||||
const newBlockNumberDec = parseInt(newBlockNumberHex, 16)
|
||||
await this._update({
|
||||
address: selectedAddress,
|
||||
newBlockNumberDec,
|
||||
})
|
||||
}
|
||||
|
||||
const initState = Object.assign({
|
||||
incomingTransactions: {},
|
||||
incomingTxLastFetchedBlocksByNetwork: {
|
||||
[ROPSTEN]: null,
|
||||
[RINKEBY]: null,
|
||||
[KOVAN]: null,
|
||||
[MAINNET]: null,
|
||||
},
|
||||
}, opts.initState)
|
||||
this.store = new ObservableStore(initState)
|
||||
|
||||
this.preferencesController.store.subscribe(pairwise((prevState, currState) => {
|
||||
const { featureFlags: { showIncomingTransactions: prevShowIncomingTransactions } = {} } = prevState
|
||||
const { featureFlags: { showIncomingTransactions: currShowIncomingTransactions } = {} } = currState
|
||||
|
||||
if (currShowIncomingTransactions === prevShowIncomingTransactions) {
|
||||
return
|
||||
}
|
||||
|
||||
if (prevShowIncomingTransactions && !currShowIncomingTransactions) {
|
||||
this.stop()
|
||||
return
|
||||
}
|
||||
|
||||
this.start()
|
||||
}))
|
||||
|
||||
this.preferencesController.store.subscribe(pairwise(async (prevState, currState) => {
|
||||
const { selectedAddress: prevSelectedAddress } = prevState
|
||||
const { selectedAddress: currSelectedAddress } = currState
|
||||
|
||||
if (currSelectedAddress === prevSelectedAddress) {
|
||||
return
|
||||
}
|
||||
|
||||
await this._update({
|
||||
address: currSelectedAddress,
|
||||
})
|
||||
}))
|
||||
|
||||
this.networkController.on('networkDidChange', async (newType) => {
|
||||
const address = this.preferencesController.getSelectedAddress()
|
||||
await this._update({
|
||||
address,
|
||||
networkType: newType,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
start () {
|
||||
const { featureFlags = {} } = this.preferencesController.store.getState()
|
||||
const { showIncomingTransactions } = featureFlags
|
||||
|
||||
if (!showIncomingTransactions) {
|
||||
return
|
||||
}
|
||||
|
||||
this.blockTracker.removeListener('latest', this._onLatestBlock)
|
||||
this.blockTracker.addListener('latest', this._onLatestBlock)
|
||||
}
|
||||
|
||||
stop () {
|
||||
this.blockTracker.removeListener('latest', this._onLatestBlock)
|
||||
}
|
||||
|
||||
async _update ({ address, newBlockNumberDec, networkType } = {}) {
|
||||
try {
|
||||
const dataForUpdate = await this._getDataForUpdate({ address, newBlockNumberDec, networkType })
|
||||
await this._updateStateWithNewTxData(dataForUpdate)
|
||||
} catch (err) {
|
||||
log.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
async _getDataForUpdate ({ address, newBlockNumberDec, networkType } = {}) {
|
||||
const {
|
||||
incomingTransactions: currentIncomingTxs,
|
||||
incomingTxLastFetchedBlocksByNetwork: currentBlocksByNetwork,
|
||||
} = this.store.getState()
|
||||
|
||||
const network = networkType || this.getCurrentNetwork()
|
||||
const lastFetchBlockByCurrentNetwork = currentBlocksByNetwork[network]
|
||||
let blockToFetchFrom = lastFetchBlockByCurrentNetwork || newBlockNumberDec
|
||||
if (blockToFetchFrom === undefined) {
|
||||
blockToFetchFrom = parseInt(this.blockTracker.getCurrentBlock(), 16)
|
||||
}
|
||||
|
||||
const { latestIncomingTxBlockNumber, txs: newTxs } = await this._fetchAll(address, blockToFetchFrom, network)
|
||||
|
||||
return {
|
||||
latestIncomingTxBlockNumber,
|
||||
newTxs,
|
||||
currentIncomingTxs,
|
||||
currentBlocksByNetwork,
|
||||
fetchedBlockNumber: blockToFetchFrom,
|
||||
network,
|
||||
}
|
||||
}
|
||||
|
||||
async _updateStateWithNewTxData ({
|
||||
latestIncomingTxBlockNumber,
|
||||
newTxs,
|
||||
currentIncomingTxs,
|
||||
currentBlocksByNetwork,
|
||||
fetchedBlockNumber,
|
||||
network,
|
||||
}) {
|
||||
const newLatestBlockHashByNetwork = latestIncomingTxBlockNumber
|
||||
? parseInt(latestIncomingTxBlockNumber, 10) + 1
|
||||
: fetchedBlockNumber + 1
|
||||
const newIncomingTransactions = {
|
||||
...currentIncomingTxs,
|
||||
}
|
||||
newTxs.forEach(tx => { newIncomingTransactions[tx.hash] = tx })
|
||||
|
||||
this.store.updateState({
|
||||
incomingTxLastFetchedBlocksByNetwork: {
|
||||
...currentBlocksByNetwork,
|
||||
[network]: newLatestBlockHashByNetwork,
|
||||
},
|
||||
incomingTransactions: newIncomingTransactions,
|
||||
})
|
||||
}
|
||||
|
||||
async _fetchAll (address, fromBlock, networkType) {
|
||||
try {
|
||||
const fetchedTxResponse = await this._fetchTxs(address, fromBlock, networkType)
|
||||
return this._processTxFetchResponse(fetchedTxResponse)
|
||||
} catch (err) {
|
||||
log.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
async _fetchTxs (address, fromBlock, networkType) {
|
||||
let etherscanSubdomain = 'api'
|
||||
const currentNetworkID = networkTypeToIdMap[networkType]
|
||||
const supportedNetworkTypes = [ROPSTEN, RINKEBY, KOVAN, MAINNET]
|
||||
|
||||
if (supportedNetworkTypes.indexOf(networkType) === -1) {
|
||||
return {}
|
||||
}
|
||||
|
||||
if (networkType !== MAINNET) {
|
||||
etherscanSubdomain = `api-${networkType}`
|
||||
}
|
||||
const apiUrl = `https://${etherscanSubdomain}.etherscan.io`
|
||||
let url = `${apiUrl}/api?module=account&action=txlist&address=${address}&tag=latest&page=1`
|
||||
|
||||
if (fromBlock) {
|
||||
url += `&startBlock=${parseInt(fromBlock, 10)}`
|
||||
}
|
||||
const response = await fetch(url)
|
||||
const parsedResponse = await response.json()
|
||||
|
||||
return {
|
||||
...parsedResponse,
|
||||
address,
|
||||
currentNetworkID,
|
||||
}
|
||||
}
|
||||
|
||||
_processTxFetchResponse ({ status, result, address, currentNetworkID }) {
|
||||
if (status !== '0' && result.length > 0) {
|
||||
const remoteTxList = {}
|
||||
const remoteTxs = []
|
||||
result.forEach((tx) => {
|
||||
if (!remoteTxList[tx.hash]) {
|
||||
remoteTxs.push(this._normalizeTxFromEtherscan(tx, currentNetworkID))
|
||||
remoteTxList[tx.hash] = 1
|
||||
}
|
||||
})
|
||||
|
||||
const incomingTxs = remoteTxs.filter(tx => tx.txParams.to && tx.txParams.to.toLowerCase() === address.toLowerCase())
|
||||
incomingTxs.sort((a, b) => (a.time < b.time ? -1 : 1))
|
||||
|
||||
let latestIncomingTxBlockNumber = null
|
||||
incomingTxs.forEach((tx) => {
|
||||
if (
|
||||
tx.blockNumber &&
|
||||
(!latestIncomingTxBlockNumber ||
|
||||
parseInt(latestIncomingTxBlockNumber, 10) < parseInt(tx.blockNumber, 10))
|
||||
) {
|
||||
latestIncomingTxBlockNumber = tx.blockNumber
|
||||
}
|
||||
})
|
||||
return {
|
||||
latestIncomingTxBlockNumber,
|
||||
txs: incomingTxs,
|
||||
}
|
||||
}
|
||||
return {
|
||||
latestIncomingTxBlockNumber: null,
|
||||
txs: [],
|
||||
}
|
||||
}
|
||||
|
||||
_normalizeTxFromEtherscan (txMeta, currentNetworkID) {
|
||||
const time = parseInt(txMeta.timeStamp, 10) * 1000
|
||||
const status = txMeta.isError === '0' ? 'confirmed' : 'failed'
|
||||
return {
|
||||
blockNumber: txMeta.blockNumber,
|
||||
id: createId(),
|
||||
metamaskNetworkId: currentNetworkID,
|
||||
status,
|
||||
time,
|
||||
txParams: {
|
||||
from: txMeta.from,
|
||||
gas: bnToHex(new BN(txMeta.gas)),
|
||||
gasPrice: bnToHex(new BN(txMeta.gasPrice)),
|
||||
nonce: bnToHex(new BN(txMeta.nonce)),
|
||||
to: txMeta.to,
|
||||
value: bnToHex(new BN(txMeta.value)),
|
||||
},
|
||||
hash: txMeta.hash,
|
||||
transactionCategory: 'incoming',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = IncomingTransactionsController
|
||||
|
||||
function pairwise (fn) {
|
||||
let first = true
|
||||
let cache
|
||||
return (value) => {
|
||||
try {
|
||||
if (first) {
|
||||
first = false
|
||||
return fn(value, value)
|
||||
} else {
|
||||
return fn(cache, value)
|
||||
}
|
||||
} finally {
|
||||
cache = value
|
||||
}
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@ function createMetamaskMiddleware ({
|
||||
processEthSignMessage,
|
||||
processTypedMessage,
|
||||
processTypedMessageV3,
|
||||
processTypedMessageV4,
|
||||
processPersonalMessage,
|
||||
getPendingNonce,
|
||||
}) {
|
||||
@ -27,6 +28,7 @@ function createMetamaskMiddleware ({
|
||||
processEthSignMessage,
|
||||
processTypedMessage,
|
||||
processTypedMessageV3,
|
||||
processTypedMessageV4,
|
||||
processPersonalMessage,
|
||||
}),
|
||||
createPendingNonceMiddleware({ getPendingNonce }),
|
||||
|
@ -41,7 +41,7 @@ class PreferencesController {
|
||||
// for convenient testing of pre-release features, and should never
|
||||
// perform sensitive operations.
|
||||
featureFlags: {
|
||||
privacyMode: true,
|
||||
showIncomingTransactions: true,
|
||||
},
|
||||
knownMethodData: {},
|
||||
participateInMetaMetrics: null,
|
||||
|
@ -6,27 +6,23 @@ const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware
|
||||
* A controller that services user-approved requests for a full Ethereum provider API
|
||||
*/
|
||||
class ProviderApprovalController extends SafeEventEmitter {
|
||||
/**
|
||||
* Determines if caching is enabled
|
||||
*/
|
||||
caching = true
|
||||
|
||||
/**
|
||||
* Creates a ProviderApprovalController
|
||||
*
|
||||
* @param {Object} [config] - Options to configure controller
|
||||
*/
|
||||
constructor ({ closePopup, keyringController, openPopup, preferencesController } = {}) {
|
||||
constructor ({ closePopup, initState, keyringController, openPopup, preferencesController } = {}) {
|
||||
super()
|
||||
this.closePopup = closePopup
|
||||
this.keyringController = keyringController
|
||||
this.openPopup = openPopup
|
||||
this.preferencesController = preferencesController
|
||||
this.store = new ObservableStore({
|
||||
approvedOrigins: {},
|
||||
dismissedOrigins: {},
|
||||
this.memStore = new ObservableStore({
|
||||
providerRequests: [],
|
||||
})
|
||||
|
||||
const defaultState = { approvedOrigins: {} }
|
||||
this.store = new ObservableStore(Object.assign(defaultState, initState))
|
||||
}
|
||||
|
||||
/**
|
||||
@ -65,11 +61,17 @@ class ProviderApprovalController extends SafeEventEmitter {
|
||||
* @param {string} siteImage - The icon of the window requesting full provider access
|
||||
*/
|
||||
_handleProviderRequest (origin, siteTitle, siteImage) {
|
||||
this.store.updateState({ providerRequests: [{ origin, siteTitle, siteImage }] })
|
||||
const { providerRequests } = this.memStore.getState()
|
||||
this.memStore.updateState({
|
||||
providerRequests: [
|
||||
...providerRequests,
|
||||
{ origin, siteTitle, siteImage },
|
||||
],
|
||||
})
|
||||
const isUnlocked = this.keyringController.memStore.getState().isUnlocked
|
||||
const { approvedOrigins, dismissedOrigins } = this.store.getState()
|
||||
const originAlreadyHandled = approvedOrigins[origin] || dismissedOrigins[origin]
|
||||
if (originAlreadyHandled && this.caching && isUnlocked) {
|
||||
const { approvedOrigins } = this.store.getState()
|
||||
const originAlreadyHandled = approvedOrigins[origin]
|
||||
if (originAlreadyHandled && isUnlocked) {
|
||||
return
|
||||
}
|
||||
this.openPopup && this.openPopup()
|
||||
@ -85,23 +87,20 @@ class ProviderApprovalController extends SafeEventEmitter {
|
||||
this.closePopup()
|
||||
}
|
||||
|
||||
const { approvedOrigins, dismissedOrigins, providerRequests } = this.store.getState()
|
||||
|
||||
let _dismissedOrigins = dismissedOrigins
|
||||
if (dismissedOrigins[origin]) {
|
||||
_dismissedOrigins = Object.assign({}, dismissedOrigins)
|
||||
delete _dismissedOrigins[origin]
|
||||
}
|
||||
|
||||
const { approvedOrigins } = this.store.getState()
|
||||
const { providerRequests } = this.memStore.getState()
|
||||
const providerRequest = providerRequests.find((request) => request.origin === origin)
|
||||
const remainingProviderRequests = providerRequests.filter(request => request.origin !== origin)
|
||||
this.store.updateState({
|
||||
approvedOrigins: {
|
||||
...approvedOrigins,
|
||||
[origin]: true,
|
||||
[origin]: {
|
||||
siteTitle: providerRequest ? providerRequest.siteTitle : null,
|
||||
siteImage: providerRequest ? providerRequest.siteImage : null,
|
||||
},
|
||||
},
|
||||
dismissedOrigins: _dismissedOrigins,
|
||||
providerRequests: remainingProviderRequests,
|
||||
})
|
||||
this.memStore.updateState({ providerRequests: remainingProviderRequests })
|
||||
this.emit(`resolvedRequest:${origin}`, { approved: true })
|
||||
}
|
||||
|
||||
@ -115,53 +114,21 @@ class ProviderApprovalController extends SafeEventEmitter {
|
||||
this.closePopup()
|
||||
}
|
||||
|
||||
const { approvedOrigins, providerRequests, dismissedOrigins } = this.store.getState()
|
||||
const { approvedOrigins } = this.store.getState()
|
||||
const { providerRequests } = this.memStore.getState()
|
||||
const remainingProviderRequests = providerRequests.filter(request => request.origin !== origin)
|
||||
|
||||
// We're cloning and deleting keys here because we don't want to keep unneeded keys
|
||||
const _approvedOrigins = Object.assign({}, approvedOrigins)
|
||||
delete _approvedOrigins[origin]
|
||||
|
||||
this.store.putState({
|
||||
approvedOrigins: _approvedOrigins,
|
||||
providerRequests: remainingProviderRequests,
|
||||
dismissedOrigins: {
|
||||
...dismissedOrigins,
|
||||
[origin]: true,
|
||||
},
|
||||
})
|
||||
this.store.putState({ approvedOrigins: _approvedOrigins })
|
||||
this.memStore.putState({ providerRequests: remainingProviderRequests })
|
||||
this.emit(`resolvedRequest:${origin}`, { approved: false })
|
||||
}
|
||||
|
||||
/**
|
||||
* Silently approves access to a full Ethereum provider API for the origin
|
||||
*
|
||||
* @param {string} origin - origin of the domain that had provider access approved
|
||||
*/
|
||||
forceApproveProviderRequestByOrigin (origin) {
|
||||
const { approvedOrigins, dismissedOrigins, providerRequests } = this.store.getState()
|
||||
const remainingProviderRequests = providerRequests.filter(request => request.origin !== origin)
|
||||
|
||||
let _dismissedOrigins = dismissedOrigins
|
||||
if (dismissedOrigins[origin]) {
|
||||
_dismissedOrigins = Object.assign({}, dismissedOrigins)
|
||||
delete _dismissedOrigins[origin]
|
||||
}
|
||||
|
||||
this.store.updateState({
|
||||
approvedOrigins: {
|
||||
...approvedOrigins,
|
||||
[origin]: true,
|
||||
},
|
||||
dismissedOrigins: _dismissedOrigins,
|
||||
providerRequests: remainingProviderRequests,
|
||||
})
|
||||
|
||||
this.emit(`forceResolvedRequest:${origin}`, { approved: true, forced: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears any cached approvals for user-approved origins
|
||||
* Clears any approvals for user-approved origins
|
||||
*/
|
||||
clearApprovedOrigins () {
|
||||
this.store.updateState({
|
||||
@ -176,10 +143,17 @@ class ProviderApprovalController extends SafeEventEmitter {
|
||||
* @returns {boolean} - True if the origin has been approved
|
||||
*/
|
||||
shouldExposeAccounts (origin) {
|
||||
const privacyMode = this.preferencesController.getFeatureFlags().privacyMode
|
||||
return !privacyMode || Boolean(this.store.getState().approvedOrigins[origin])
|
||||
return Boolean(this.store.getState().approvedOrigins[origin])
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a merged state representation
|
||||
* @return {object}
|
||||
* @private
|
||||
*/
|
||||
_getMergedState () {
|
||||
return Object.assign({}, this.memStore.getState(), this.store.getState())
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ProviderApprovalController
|
||||
|
@ -61,8 +61,19 @@ const inpageProvider = new MetamaskInpageProvider(metamaskStream)
|
||||
// set a high max listener count to avoid unnecesary warnings
|
||||
inpageProvider.setMaxListeners(100)
|
||||
|
||||
let warnedOfAutoRefreshDeprecation = false
|
||||
// augment the provider with its enable method
|
||||
inpageProvider.enable = function ({ force } = {}) {
|
||||
if (
|
||||
!warnedOfAutoRefreshDeprecation &&
|
||||
inpageProvider.autoRefreshOnNetworkChange
|
||||
) {
|
||||
console.warn(`MetaMask: MetaMask will soon stop reloading pages on network change.
|
||||
If you rely upon this behavior, add a 'networkChanged' event handler to trigger the reload manually: https://metamask.github.io/metamask-docs/API_Reference/Ethereum_Provider#ethereum.on(eventname%2C-callback)
|
||||
Set 'ethereum.autoRefreshOnNetworkChange' to 'false' to silence this warning: https://metamask.github.io/metamask-docs/API_Reference/Ethereum_Provider#ethereum.autorefreshonnetworkchange'
|
||||
`)
|
||||
warnedOfAutoRefreshDeprecation = true
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
inpageProvider.sendAsync({ method: 'eth_requestAccounts', params: [force] }, (error, response) => {
|
||||
if (error || response.error) {
|
||||
|
@ -141,6 +141,7 @@ module.exports = class TypedMessageManager extends EventEmitter {
|
||||
}, 'Expected EIP712 typed data')
|
||||
break
|
||||
case 'V3':
|
||||
case 'V4':
|
||||
let data
|
||||
assert.equal(typeof params, 'object', 'Params should be an object.')
|
||||
assert.ok('data' in params, 'Params must include a data field.')
|
||||
|
@ -144,6 +144,29 @@ function removeListeners (listeners, emitter) {
|
||||
})
|
||||
}
|
||||
|
||||
function fetchWithTimeout ({ timeout = 120000 } = {}) {
|
||||
return async function _fetch (url, opts) {
|
||||
const abortController = new AbortController()
|
||||
const abortSignal = abortController.signal
|
||||
const f = fetch(url, {
|
||||
...opts,
|
||||
signal: abortSignal,
|
||||
})
|
||||
|
||||
const timer = setTimeout(() => abortController.abort(), timeout)
|
||||
|
||||
try {
|
||||
const res = await f
|
||||
clearTimeout(timer)
|
||||
return res
|
||||
} catch (e) {
|
||||
clearTimeout(timer)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
removeListeners,
|
||||
applyListeners,
|
||||
@ -154,4 +177,5 @@ module.exports = {
|
||||
hexToBn,
|
||||
bnToHex,
|
||||
BnMultiplyByFraction,
|
||||
fetchWithTimeout,
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ const InfuraController = require('./controllers/infura')
|
||||
const CachedBalancesController = require('./controllers/cached-balances')
|
||||
const OnboardingController = require('./controllers/onboarding')
|
||||
const RecentBlocksController = require('./controllers/recent-blocks')
|
||||
const IncomingTransactionsController = require('./controllers/incoming-transactions')
|
||||
const MessageManager = require('./lib/message-manager')
|
||||
const PersonalMessageManager = require('./lib/personal-message-manager')
|
||||
const TypedMessageManager = require('./lib/typed-message-manager')
|
||||
@ -54,6 +55,7 @@ const HW_WALLETS_KEYRINGS = [TrezorKeyring.type, LedgerBridgeKeyring.type]
|
||||
const EthQuery = require('eth-query')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const sigUtil = require('eth-sig-util')
|
||||
const contractMap = require('eth-contract-metadata')
|
||||
const {
|
||||
AddressBookController,
|
||||
CurrencyRateController,
|
||||
@ -62,7 +64,6 @@ const {
|
||||
} = require('gaba')
|
||||
const backEndMetaMetricsEvent = require('./lib/backend-metametrics')
|
||||
|
||||
|
||||
module.exports = class MetamaskController extends EventEmitter {
|
||||
|
||||
/**
|
||||
@ -137,6 +138,13 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
networkController: this.networkController,
|
||||
})
|
||||
|
||||
this.incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: this.blockTracker,
|
||||
networkController: this.networkController,
|
||||
preferencesController: this.preferencesController,
|
||||
initState: initState.IncomingTransactionsController,
|
||||
})
|
||||
|
||||
// account tracker watches balances, nonces, and any code at their address.
|
||||
this.accountTracker = new AccountTracker({
|
||||
provider: this.provider,
|
||||
@ -148,8 +156,10 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
this.on('controllerConnectionChanged', (activeControllerConnections) => {
|
||||
if (activeControllerConnections > 0) {
|
||||
this.accountTracker.start()
|
||||
this.incomingTransactionsController.start()
|
||||
} else {
|
||||
this.accountTracker.stop()
|
||||
this.incomingTransactionsController.stop()
|
||||
}
|
||||
})
|
||||
|
||||
@ -251,6 +261,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
|
||||
this.providerApprovalController = new ProviderApprovalController({
|
||||
closePopup: opts.closePopup,
|
||||
initState: initState.ProviderApprovalController,
|
||||
keyringController: this.keyringController,
|
||||
openPopup: opts.openPopup,
|
||||
preferencesController: this.preferencesController,
|
||||
@ -268,6 +279,8 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
InfuraController: this.infuraController.store,
|
||||
CachedBalancesController: this.cachedBalancesController.store,
|
||||
OnboardingController: this.onboardingController.store,
|
||||
ProviderApprovalController: this.providerApprovalController.store,
|
||||
IncomingTransactionsController: this.incomingTransactionsController.store,
|
||||
})
|
||||
|
||||
this.memStore = new ComposableObservableStore(null, {
|
||||
@ -288,8 +301,11 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
CurrencyController: this.currencyRateController,
|
||||
ShapeshiftController: this.shapeshiftController,
|
||||
InfuraController: this.infuraController.store,
|
||||
ProviderApprovalController: this.providerApprovalController.store,
|
||||
OnboardingController: this.onboardingController.store,
|
||||
// ProviderApprovalController
|
||||
ProviderApprovalController: this.providerApprovalController.store,
|
||||
ProviderApprovalControllerMemStore: this.providerApprovalController.memStore,
|
||||
IncomingTransactionsController: this.incomingTransactionsController.store,
|
||||
})
|
||||
this.memStore.subscribe(this.sendUpdate.bind(this))
|
||||
}
|
||||
@ -325,6 +341,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
processEthSignMessage: this.newUnsignedMessage.bind(this),
|
||||
processTypedMessage: this.newUnsignedTypedMessage.bind(this),
|
||||
processTypedMessageV3: this.newUnsignedTypedMessage.bind(this),
|
||||
processTypedMessageV4: this.newUnsignedTypedMessage.bind(this),
|
||||
processPersonalMessage: this.newUnsignedPersonalMessage.bind(this),
|
||||
getPendingNonce: this.getPendingNonce.bind(this),
|
||||
}
|
||||
@ -385,10 +402,6 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
return {
|
||||
...{ isInitialized },
|
||||
...this.memStore.getFlatState(),
|
||||
...{
|
||||
// TODO: Remove usages of lost accounts
|
||||
lostAccounts: [],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -468,7 +481,7 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
whitelistPhishingDomain: this.whitelistPhishingDomain.bind(this),
|
||||
|
||||
// AddressController
|
||||
setAddressBook: this.addressBookController.set.bind(this.addressBookController),
|
||||
setAddressBook: nodeify(this.addressBookController.set, this.addressBookController),
|
||||
removeFromAddressBook: this.addressBookController.delete.bind(this.addressBookController),
|
||||
|
||||
// AppStateController
|
||||
@ -507,7 +520,6 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
// provider approval
|
||||
approveProviderRequestByOrigin: providerApprovalController.approveProviderRequestByOrigin.bind(providerApprovalController),
|
||||
rejectProviderRequestByOrigin: providerApprovalController.rejectProviderRequestByOrigin.bind(providerApprovalController),
|
||||
forceApproveProviderRequestByOrigin: providerApprovalController.forceApproveProviderRequestByOrigin.bind(providerApprovalController),
|
||||
clearApprovedOrigins: providerApprovalController.clearApprovedOrigins.bind(providerApprovalController),
|
||||
|
||||
// onboarding controller
|
||||
@ -639,8 +651,24 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
tokens,
|
||||
} = this.preferencesController.store.getState()
|
||||
|
||||
// Filter ERC20 tokens
|
||||
const filteredAccountTokens = {}
|
||||
Object.keys(accountTokens).forEach(address => {
|
||||
const checksummedAddress = ethUtil.toChecksumAddress(address)
|
||||
filteredAccountTokens[checksummedAddress] = {}
|
||||
Object.keys(accountTokens[address]).forEach(
|
||||
networkType => (filteredAccountTokens[checksummedAddress][networkType] = networkType !== 'mainnet' ?
|
||||
accountTokens[address][networkType] :
|
||||
accountTokens[address][networkType].filter(({ address }) => {
|
||||
const tokenAddress = ethUtil.toChecksumAddress(address)
|
||||
return contractMap[tokenAddress] ? contractMap[tokenAddress].erc20 : true
|
||||
})
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
const preferences = {
|
||||
accountTokens,
|
||||
accountTokens: filteredAccountTokens,
|
||||
currentLocale,
|
||||
frequentRpcList,
|
||||
identities,
|
||||
@ -1114,6 +1142,9 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
case 'V3':
|
||||
signature = sigUtil.signTypedData(privKey, { data: JSON.parse(cleanMsgParams.data) })
|
||||
break
|
||||
case 'V4':
|
||||
signature = sigUtil.signTypedData_v4(privKey, { data: JSON.parse(cleanMsgParams.data) })
|
||||
break
|
||||
}
|
||||
} else {
|
||||
signature = await keyring.signTypedData(address, cleanMsgParams.data)
|
||||
@ -1177,27 +1208,6 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
* @property string privateKey - The private key of the account.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Probably no longer needed, related to the Version 3 migration.
|
||||
* Imports a hash of accounts to private keys into the vault.
|
||||
*
|
||||
* Described in:
|
||||
* https://medium.com/metamask/metamask-3-migration-guide-914b79533cdd
|
||||
*
|
||||
* Uses the array's private keys to create a new Simple Key Pair keychain
|
||||
* and add it to the keyring controller.
|
||||
* @deprecated
|
||||
* @param {Account[]} lostAccounts -
|
||||
* @returns {Keyring[]} An array of the restored keyrings.
|
||||
*/
|
||||
importLostAccounts ({ lostAccounts }) {
|
||||
const privKeys = lostAccounts.map(acct => acct.privateKey)
|
||||
return this.keyringController.restoreKeyring({
|
||||
type: 'Simple Key Pair',
|
||||
data: privKeys,
|
||||
})
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// END (VAULT / KEYRING RELATED METHODS)
|
||||
//=============================================================================
|
||||
@ -1298,8 +1308,6 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
const publicApi = this.setupPublicApi(mux.createStream('publicApi'), originDomain)
|
||||
this.setupProviderConnection(mux.createStream('provider'), originDomain, publicApi)
|
||||
this.setupPublicConfig(mux.createStream('publicConfig'), originDomain)
|
||||
|
||||
this.providerApprovalController.on(`forceResolvedRequest:${originDomain}`, publicApi.forceReloadSite)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1480,10 +1488,6 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
|
||||
const publicApi = {
|
||||
// wrap with an await remote
|
||||
forceReloadSite: async () => {
|
||||
const remote = await getRemote()
|
||||
return await pify(remote.forceReloadSite)()
|
||||
},
|
||||
getSiteMetadata: async () => {
|
||||
const remote = await getRemote()
|
||||
return await pify(remote.getSiteMetadata)()
|
||||
@ -1797,4 +1801,3 @@ module.exports = class MetamaskController extends EventEmitter {
|
||||
return this.keyringController.setLocked()
|
||||
}
|
||||
}
|
||||
|
||||
|
30
app/scripts/migrations/036.js
Normal file
@ -0,0 +1,30 @@
|
||||
const version = 36
|
||||
const clone = require('clone')
|
||||
|
||||
/**
|
||||
* The purpose of this migration is to remove the {@code privacyMode} feature flag.
|
||||
*/
|
||||
module.exports = {
|
||||
version,
|
||||
migrate: async function (originalVersionedData) {
|
||||
const versionedData = clone(originalVersionedData)
|
||||
versionedData.meta.version = version
|
||||
const state = versionedData.data
|
||||
versionedData.data = transformState(state)
|
||||
return versionedData
|
||||
},
|
||||
}
|
||||
|
||||
function transformState (state) {
|
||||
const { PreferencesController } = state
|
||||
|
||||
if (PreferencesController) {
|
||||
const featureFlags = PreferencesController.featureFlags || {}
|
||||
|
||||
if (typeof featureFlags.privacyMode !== 'undefined') {
|
||||
delete featureFlags.privacyMode
|
||||
}
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
@ -44,4 +44,7 @@ module.exports = [
|
||||
require('./031'),
|
||||
require('./032'),
|
||||
require('./033'),
|
||||
require('./034'),
|
||||
require('./035'),
|
||||
require('./036'),
|
||||
]
|
||||
|
@ -60,7 +60,6 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"lostAccounts": [],
|
||||
"seedWords": null
|
||||
},
|
||||
"appState": {
|
||||
|
@ -102,8 +102,7 @@
|
||||
"aa25854c0379e53c957ac9382e720c577fa31fd5"
|
||||
]
|
||||
}
|
||||
],
|
||||
"lostAccounts": []
|
||||
]
|
||||
},
|
||||
"appState": {
|
||||
"menuOpen": false,
|
||||
|
@ -93,7 +93,6 @@
|
||||
"type": "testnet"
|
||||
},
|
||||
"shapeShiftTxList": [],
|
||||
"lostAccounts": [],
|
||||
"send": {
|
||||
"gasLimit": null,
|
||||
"gasPrice": null,
|
||||
@ -104,7 +103,7 @@
|
||||
"amount": "0x0",
|
||||
"memo": "",
|
||||
"errors": {},
|
||||
"warnings": {},
|
||||
"warnings": {},
|
||||
"maxModeOn": false,
|
||||
"editingTransactionId": null
|
||||
},
|
||||
|
@ -103,8 +103,7 @@
|
||||
"keyringTypes": [
|
||||
"Simple Key Pair",
|
||||
"HD Key Tree"
|
||||
],
|
||||
"lostAccounts": []
|
||||
]
|
||||
},
|
||||
"appState": {
|
||||
"menuOpen": false,
|
||||
|
@ -191,7 +191,6 @@
|
||||
"type": "testnet"
|
||||
},
|
||||
"shapeShiftTxList": [],
|
||||
"lostAccounts": [],
|
||||
"frequentRpcListDetail": []
|
||||
},
|
||||
"appState": {
|
||||
|
@ -110,7 +110,6 @@
|
||||
"type": "testnet"
|
||||
},
|
||||
"shapeShiftTxList": [],
|
||||
"lostAccounts": [],
|
||||
"send": {
|
||||
"gasLimit": "0xea60",
|
||||
"gasPrice": "0xba43b7400",
|
||||
|
@ -63,6 +63,7 @@
|
||||
],
|
||||
"tokens": [],
|
||||
"transactions": {},
|
||||
"incomingTransactions": {},
|
||||
"selectedAddressTxList": [],
|
||||
"unapprovedTxs": {},
|
||||
"unapprovedMsgs": {
|
||||
@ -133,7 +134,6 @@
|
||||
"type": "testnet"
|
||||
},
|
||||
"shapeShiftTxList": [],
|
||||
"lostAccounts": [],
|
||||
"send": {
|
||||
"gasLimit": "0xea60",
|
||||
"gasPrice": "0xba43b7400",
|
||||
|
@ -64,6 +64,7 @@
|
||||
],
|
||||
"tokens": [],
|
||||
"transactions": {},
|
||||
"incomingTransactions": {},
|
||||
"selectedAddressTxList": [],
|
||||
"unapprovedMsgs": {},
|
||||
"unapprovedMsgCount": 0,
|
||||
@ -95,7 +96,6 @@
|
||||
"type": "testnet"
|
||||
},
|
||||
"shapeShiftTxList": [],
|
||||
"lostAccounts": [],
|
||||
"send": {
|
||||
"gasLimit": null,
|
||||
"gasPrice": null,
|
||||
|
@ -34,7 +34,6 @@
|
||||
"type": "testnet"
|
||||
},
|
||||
"shapeShiftTxList": [],
|
||||
"lostAccounts": [],
|
||||
"tokens": [],
|
||||
"currentLocale": "en",
|
||||
"preferences": {
|
||||
|
@ -72,8 +72,7 @@
|
||||
"01208723ba84e15da2e71656544a2963b0c06d40"
|
||||
]
|
||||
}
|
||||
],
|
||||
"lostAccounts": []
|
||||
]
|
||||
},
|
||||
"appState": {
|
||||
"menuOpen": false,
|
||||
|
@ -44,8 +44,7 @@
|
||||
"01208723ba84e15da2e71656544a2963b0c06d40"
|
||||
]
|
||||
}
|
||||
],
|
||||
"lostAccounts": []
|
||||
]
|
||||
},
|
||||
"appState": {
|
||||
"menuOpen": false,
|
||||
|
@ -1,89 +0,0 @@
|
||||
{
|
||||
"metamask": {
|
||||
"currentCurrency": "USD",
|
||||
"lostAccounts": [
|
||||
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
|
||||
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b"
|
||||
],
|
||||
"conversionRate": 11.06608791,
|
||||
"conversionDate": 1470421024,
|
||||
"isInitialized": true,
|
||||
"isUnlocked": true,
|
||||
"currentDomain": "example.com",
|
||||
"rpcTarget": "https://rawtestrpc.metamask.io/",
|
||||
"identities": {
|
||||
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": {
|
||||
"name": "Wallet 1",
|
||||
"address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc",
|
||||
"mayBeFauceting": false
|
||||
},
|
||||
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": {
|
||||
"name": "Wallet 2",
|
||||
"address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b",
|
||||
"mayBeFauceting": false
|
||||
},
|
||||
"0xeb9e64b93097bc15f01f13eae97015c57ab64823": {
|
||||
"name": "Wallet 3",
|
||||
"address": "0xeb9e64b93097bc15f01f13eae97015c57ab64823",
|
||||
"mayBeFauceting": false
|
||||
},
|
||||
"0x704107d04affddd9b66ab9de3dd7b095852e9b69": {
|
||||
"name": "Wallet 4",
|
||||
"address": "0x704107d04affddd9b66ab9de3dd7b095852e9b69",
|
||||
"mayBeFauceting": false
|
||||
}
|
||||
},
|
||||
"unconfTxs": {},
|
||||
"accounts": {
|
||||
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": {
|
||||
"code": "0x",
|
||||
"balance": "0x100000000000",
|
||||
"nonce": "0x0",
|
||||
"address": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
|
||||
},
|
||||
"0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b": {
|
||||
"code": "0x",
|
||||
"nonce": "0x0",
|
||||
"balance": "0x100000000000",
|
||||
"address": "0xec1adf982415d2ef5ec55899b9bfb8bc0f29251b"
|
||||
},
|
||||
"0xeb9e64b93097bc15f01f13eae97015c57ab64823": {
|
||||
"code": "0x",
|
||||
"nonce": "0x0",
|
||||
"balance": "0x100000000000",
|
||||
"address": "0xeb9e64b93097bc15f01f13eae97015c57ab64823"
|
||||
},
|
||||
"0x704107d04affddd9b66ab9de3dd7b095852e9b69": {
|
||||
"code": "0x",
|
||||
"balance": "0x0",
|
||||
"nonce": "0x0",
|
||||
"address": "0x704107d04affddd9b66ab9de3dd7b095852e9b69"
|
||||
}
|
||||
},
|
||||
"transactions": [],
|
||||
"network": "2",
|
||||
"seedWords": null,
|
||||
"unconfMsgs": {},
|
||||
"messages": [],
|
||||
"provider": {
|
||||
"type": "testnet"
|
||||
},
|
||||
"selectedAddress": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
|
||||
},
|
||||
"appState": {
|
||||
"menuOpen": false,
|
||||
"currentView": {
|
||||
"name": "accountDetail",
|
||||
"detailView": null,
|
||||
"context": "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc"
|
||||
},
|
||||
"accountDetail": {
|
||||
"subview": "transactions"
|
||||
},
|
||||
"currentDomain": "127.0.0.1:9966",
|
||||
"transForward": true,
|
||||
"isLoading": false,
|
||||
"warning": null
|
||||
},
|
||||
"identities": {}
|
||||
}
|
@ -232,8 +232,7 @@
|
||||
"rinkeby": "ok",
|
||||
"ropsten": "ok",
|
||||
"goerli": "ok"
|
||||
},
|
||||
"lostAccounts": []
|
||||
}
|
||||
},
|
||||
"appState": {
|
||||
"shouldClose": false,
|
||||
|
@ -86,7 +86,6 @@
|
||||
"type": "testnet"
|
||||
},
|
||||
"shapeShiftTxList": [],
|
||||
"lostAccounts": [],
|
||||
"seedWords": null
|
||||
},
|
||||
"appState": {
|
||||
|
@ -707,8 +707,7 @@
|
||||
"rinkeby": "ok",
|
||||
"goerli": "ok"
|
||||
},
|
||||
"shapeShiftTxList": [],
|
||||
"lostAccounts": []
|
||||
"shapeShiftTxList": []
|
||||
},
|
||||
"appState": {
|
||||
"shouldClose": true,
|
||||
|
@ -78,8 +78,7 @@
|
||||
"provider": {
|
||||
"type": "testnet"
|
||||
},
|
||||
"shapeShiftTxList": [],
|
||||
"lostAccounts": []
|
||||
"shapeShiftTxList": []
|
||||
},
|
||||
"appState": {
|
||||
"menuOpen": false,
|
||||
|
@ -48,7 +48,6 @@
|
||||
"type": "testnet"
|
||||
},
|
||||
"shapeShiftTxList": [],
|
||||
"lostAccounts": [],
|
||||
"seedWords": null
|
||||
},
|
||||
"appState": {
|
||||
|
@ -48,7 +48,6 @@
|
||||
"type": "testnet"
|
||||
},
|
||||
"shapeShiftTxList": [],
|
||||
"lostAccounts": [],
|
||||
"seedWords": null
|
||||
},
|
||||
"appState": {
|
||||
|
@ -128,7 +128,6 @@
|
||||
"type": "testnet"
|
||||
},
|
||||
"shapeShiftTxList": [],
|
||||
"lostAccounts": [],
|
||||
"send": {
|
||||
"gasLimit": "0xea60",
|
||||
"gasPrice": "0xba43b7400",
|
||||
|
@ -28,6 +28,7 @@
|
||||
"conversionRate": 1200.88200327,
|
||||
"conversionDate": 1489013762,
|
||||
"noActiveNotices": true,
|
||||
"incomingTransactions": {},
|
||||
"frequentRpcList": [],
|
||||
"network": "3",
|
||||
"accounts": {
|
||||
@ -96,7 +97,6 @@
|
||||
"type": "testnet"
|
||||
},
|
||||
"shapeShiftTxList": [],
|
||||
"lostAccounts": [],
|
||||
"send": {
|
||||
"gasLimit": null,
|
||||
"gasPrice": null,
|
||||
|
@ -86,7 +86,6 @@
|
||||
"type": "testnet"
|
||||
},
|
||||
"shapeShiftTxList": [],
|
||||
"lostAccounts": [],
|
||||
"frequentRpcListDetail": []
|
||||
},
|
||||
"appState": {
|
||||
|
@ -64,6 +64,7 @@
|
||||
],
|
||||
"tokens": [],
|
||||
"transactions": {},
|
||||
"incomingTransactions": {},
|
||||
"selectedAddressTxList": [
|
||||
{
|
||||
"err": {
|
||||
@ -1053,7 +1054,6 @@
|
||||
{"depositAddress":"34vJ3AfmNcLiziA4VFgEVcQTwxVLD1qkke","depositType":"BTC","key":"shapeshift","response":{"status":"no_deposits","address":"34vJ3AfmNcLiziA4VFgEVcQTwxVLD1qkke"},"time":1522347459106},
|
||||
{"depositAddress":"34vJ3AfmNcLiziA4VFgEVcQTwxVLD1qkkq","depositType":"BTC","key":"shapeshift","response":{"status":"no_deposits","address":"34vJ3AfmNcLiziA4VFgEVcQTwxVLD1qkkq"},"time":1522345459106}
|
||||
],
|
||||
"lostAccounts": [],
|
||||
"send": {},
|
||||
"currentLocale": "en",
|
||||
"preferences": {
|
||||
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 138 KiB |
241
gentests.js
@ -1,241 +0,0 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const async = require('async')
|
||||
const promisify = require('pify')
|
||||
|
||||
// start(/\.selectors.js/, generateSelectorTest).catch(console.error)
|
||||
// start(/\.utils.js/, generateUtilTest).catch(console.error)
|
||||
startContainer(/\.container.js/, generateContainerTest).catch(console.error)
|
||||
|
||||
async function getAllFileNames (dirName) {
|
||||
const allNames = (await promisify(fs.readdir)(dirName))
|
||||
const fileNames = allNames.filter(name => name.match(/^.+\./))
|
||||
const dirNames = allNames.filter(name => name.match(/^[^.]+$/))
|
||||
|
||||
const fullPathDirNames = dirNames.map(d => `${dirName}/${d}`)
|
||||
const subNameArrays = await promisify(async.map)(fullPathDirNames, getAllFileNames)
|
||||
let subNames = []
|
||||
subNameArrays.forEach(subNameArray => { subNames = [...subNames, ...subNameArray] })
|
||||
|
||||
return [
|
||||
...fileNames.map(name => dirName + '/' + name),
|
||||
...subNames,
|
||||
]
|
||||
}
|
||||
|
||||
/*
|
||||
async function start (fileRegEx, testGenerator) {
|
||||
const fileNames = await getAllFileNames('./ui/app')
|
||||
const sFiles = fileNames.filter(name => name.match(fileRegEx))
|
||||
|
||||
let sFileMethodNames
|
||||
let testFilePath
|
||||
async.each(sFiles, async (sFile, cb) => {
|
||||
const [, sRootPath, sPath] = sFile.match(/^(.+\/)([^/]+)$/)
|
||||
sFileMethodNames = Object.keys(require(__dirname + '/' + sFile))
|
||||
|
||||
testFilePath = sPath.replace('.', '-').replace('.', '.test.')
|
||||
|
||||
await promisify(fs.writeFile)(
|
||||
`${__dirname}/${sRootPath}tests/${testFilePath}`,
|
||||
testGenerator(sPath, sFileMethodNames),
|
||||
'utf8'
|
||||
)
|
||||
}, (err) => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
async function startContainer (fileRegEx) {
|
||||
const fileNames = await getAllFileNames('./ui/app')
|
||||
const sFiles = fileNames.filter(name => name.match(fileRegEx))
|
||||
|
||||
async.each(sFiles, async (sFile) => {
|
||||
console.log(`sFile`, sFile)
|
||||
const [, sRootPath, sPath] = sFile.match(/^(.+\/)([^/]+)$/)
|
||||
|
||||
const testFilePath = sPath.replace('.', '-').replace('.', '.test.')
|
||||
|
||||
await promisify(fs.readFile)(
|
||||
path.join(__dirname, sFile),
|
||||
'utf8',
|
||||
async (err, result) => {
|
||||
if (err) {
|
||||
console.log('Error: ', err)
|
||||
} else {
|
||||
console.log(`result`, result.length)
|
||||
const returnObjectStrings = result
|
||||
.match(/return\s(\{[\s\S]+?})\n}/g)
|
||||
.map(str => {
|
||||
return str
|
||||
.slice(0, str.length - 1)
|
||||
.slice(7)
|
||||
.replace(/\n/g, '')
|
||||
.replace(/\s\s+/g, ' ')
|
||||
|
||||
})
|
||||
const mapStateToPropsAssertionObject = returnObjectStrings[0]
|
||||
.replace(/\w+:\s\w+\([\w,\s]+\),/g, str => {
|
||||
const strKey = str.match(/^\w+/)[0]
|
||||
return strKey + ': \'mock' + str.match(/^\w+/)[0].replace(/^./, c => c.toUpperCase()) + ':mockState\',\n'
|
||||
})
|
||||
.replace(/{\s\w.+/, firstLinePair => `{\n ${firstLinePair.slice(2)}`)
|
||||
.replace(/\w+:.+,/g, s => ` ${s}`)
|
||||
.replace(/}/g, s => ` ${s}`)
|
||||
let mapDispatchToPropsMethodNames
|
||||
if (returnObjectStrings[1]) {
|
||||
mapDispatchToPropsMethodNames = returnObjectStrings[1].match(/\s\w+:\s/g).map(str => str.match(/\w+/)[0])
|
||||
}
|
||||
const proxyquireObject = ('{\n ' + result
|
||||
.match(/import\s{[\s\S]+?}\sfrom\s.+/g)
|
||||
.map(s => s.replace(/\n/g, ''))
|
||||
.map((s) => {
|
||||
const proxyKeys = s.match(/{.+}/)[0].match(/\w+/g)
|
||||
return '\'' + s.match(/'(.+)'/)[1] + '\': { ' + (proxyKeys.length > 1
|
||||
? '\n ' + proxyKeys.join(': () => {},\n ') + ': () => {},\n '
|
||||
: proxyKeys[0] + ': () => {},') + ' }'
|
||||
})
|
||||
.join(',\n ') + '\n}')
|
||||
.replace('{ connect: () => {}, },', `{
|
||||
connect: (ms, md) => {
|
||||
mapStateToProps = ms
|
||||
mapDispatchToProps = md
|
||||
return () => ({})
|
||||
},
|
||||
},`)
|
||||
// console.log(`proxyquireObject`, proxyquireObject);
|
||||
// console.log(`mapStateToPropsAssertionObject`, mapStateToPropsAssertionObject);
|
||||
// console.log(`mapDispatchToPropsMethodNames`, mapDispatchToPropsMethodNames);
|
||||
|
||||
const containerTest = generateContainerTest(sPath, {
|
||||
mapStateToPropsAssertionObject,
|
||||
mapDispatchToPropsMethodNames,
|
||||
proxyquireObject,
|
||||
})
|
||||
// console.log(`containerTest`, `${__dirname}/${sRootPath}tests/${testFilePath}`, containerTest);
|
||||
console.log('----')
|
||||
console.log(`sRootPath`, sRootPath)
|
||||
console.log(`testFilePath`, testFilePath)
|
||||
await promisify(fs.writeFile)(
|
||||
`${__dirname}/${sRootPath}tests/${testFilePath}`,
|
||||
containerTest,
|
||||
'utf8'
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}, (err) => {
|
||||
console.log('123', err)
|
||||
})
|
||||
|
||||
}
|
||||
/*
|
||||
function generateMethodList (methodArray) {
|
||||
return methodArray.map(n => ' ' + n).join(',\n') + ','
|
||||
}
|
||||
|
||||
function generateMethodDescribeBlock (methodName, index) {
|
||||
const describeBlock =
|
||||
`${index ? ' ' : ''}describe('${methodName}()', () => {
|
||||
it('should', () => {
|
||||
const state = {}
|
||||
|
||||
assert.equal(${methodName}(state), )
|
||||
})
|
||||
})`
|
||||
return describeBlock
|
||||
}
|
||||
*/
|
||||
function generateDispatchMethodDescribeBlock (methodName, index) {
|
||||
const describeBlock =
|
||||
`${index ? ' ' : ''}describe('${methodName}()', () => {
|
||||
it('should dispatch an action', () => {
|
||||
mapDispatchToPropsObject.${methodName}()
|
||||
assert(dispatchSpy.calledOnce)
|
||||
})
|
||||
})`
|
||||
return describeBlock
|
||||
}
|
||||
/*
|
||||
function generateMethodDescribeBlocks (methodArray) {
|
||||
return methodArray
|
||||
.map((methodName, index) => generateMethodDescribeBlock(methodName, index))
|
||||
.join('\n\n')
|
||||
}
|
||||
*/
|
||||
|
||||
function generateDispatchMethodDescribeBlocks (methodArray) {
|
||||
return methodArray
|
||||
.map((methodName, index) => generateDispatchMethodDescribeBlock(methodName, index))
|
||||
.join('\n\n')
|
||||
}
|
||||
|
||||
/*
|
||||
function generateSelectorTest (name, methodArray) {
|
||||
return `import assert from 'assert'
|
||||
import {
|
||||
${generateMethodList(methodArray)}
|
||||
} from '../${name}'
|
||||
|
||||
describe('${name.match(/^[^.]+/)} selectors', () => {
|
||||
|
||||
${generateMethodDescribeBlocks(methodArray)}
|
||||
|
||||
})`
|
||||
}
|
||||
|
||||
function generateUtilTest (name, methodArray) {
|
||||
return `import assert from 'assert'
|
||||
import {
|
||||
${generateMethodList(methodArray)}
|
||||
} from '../${name}'
|
||||
|
||||
describe('${name.match(/^[^.]+/)} utils', () => {
|
||||
|
||||
${generateMethodDescribeBlocks(methodArray)}
|
||||
|
||||
})`
|
||||
}
|
||||
*/
|
||||
|
||||
function generateContainerTest (sPath, {
|
||||
mapStateToPropsAssertionObject,
|
||||
mapDispatchToPropsMethodNames,
|
||||
proxyquireObject,
|
||||
}) {
|
||||
return `import assert from 'assert'
|
||||
import proxyquire from 'proxyquire'
|
||||
import sinon from 'sinon'
|
||||
|
||||
let mapStateToProps
|
||||
let mapDispatchToProps
|
||||
|
||||
proxyquire('../${sPath}', ${proxyquireObject})
|
||||
|
||||
describe('${sPath.match(/^[^.]+/)} container', () => {
|
||||
|
||||
describe('mapStateToProps()', () => {
|
||||
|
||||
it('should map the correct properties to props', () => {
|
||||
assert.deepEqual(mapStateToProps('mockState'), ${mapStateToPropsAssertionObject})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('mapDispatchToProps()', () => {
|
||||
let dispatchSpy
|
||||
let mapDispatchToPropsObject
|
||||
|
||||
beforeEach(() => {
|
||||
dispatchSpy = sinon.spy()
|
||||
mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy)
|
||||
})
|
||||
|
||||
${mapDispatchToPropsMethodNames ? generateDispatchMethodDescribeBlocks(mapDispatchToPropsMethodNames) : 'delete'}
|
||||
|
||||
})
|
||||
|
||||
})`
|
||||
}
|
@ -22,7 +22,7 @@
|
||||
"test:web3:chrome": "SELENIUM_BROWSER=chrome test/e2e/run-web3.sh",
|
||||
"test:web3:firefox": "SELENIUM_BROWSER=firefox test/e2e/run-web3.sh",
|
||||
"test:e2e:firefox": "SELENIUM_BROWSER=firefox test/e2e/run-all.sh",
|
||||
"test:coverage": "nyc --reporter=text --reporter=html npm run test:unit && yarn test:coveralls-upload",
|
||||
"test:coverage": "nyc --reporter=text --reporter=html npm run test:unit",
|
||||
"test:coveralls-upload": "if [ $COVERALLS_REPO_TOKEN ]; then nyc report --reporter=text-lcov | coveralls; fi",
|
||||
"test:flat": "yarn test:flat:build && karma start test/flat.conf.js",
|
||||
"test:flat:build": "yarn test:flat:build:ui && yarn test:flat:build:tests && yarn test:flat:build:locales",
|
||||
|
@ -64,7 +64,6 @@
|
||||
"noActiveNotices": true,
|
||||
"shapeShiftTxList": [],
|
||||
"infuraNetworkStatus": {},
|
||||
"lostAccounts": [],
|
||||
"seedWords": "debris dizzy just program just float decrease vacant alarm reduce speak stadium",
|
||||
"forgottenPassword": null
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,8 @@
|
||||
{
|
||||
"metamask": {
|
||||
"featureFlags": {
|
||||
"showIncomingTransactions": true
|
||||
},
|
||||
"network": "4",
|
||||
"identities": {
|
||||
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": {
|
||||
@ -12,6 +15,7 @@
|
||||
}
|
||||
},
|
||||
"cachedBalances": {},
|
||||
"incomingTransactions": {},
|
||||
"unapprovedTxs": {
|
||||
"8393540981007587": {
|
||||
"id": 8393540981007587,
|
||||
|
@ -1247,7 +1247,9 @@ describe('MetaMask', function () {
|
||||
const transferTokens = await findElement(driver, By.xpath(`//button[contains(text(), 'Approve Tokens')]`))
|
||||
await transferTokens.click()
|
||||
|
||||
await closeAllWindowHandlesExcept(driver, [extension, dapp])
|
||||
if (process.env.SELENIUM_BROWSER !== 'firefox') {
|
||||
await closeAllWindowHandlesExcept(driver, [extension, dapp])
|
||||
}
|
||||
await driver.switchTo().window(extension)
|
||||
await delay(regularDelayMs)
|
||||
|
||||
@ -1341,6 +1343,10 @@ describe('MetaMask', function () {
|
||||
})
|
||||
|
||||
it('finds the transaction in the transactions list', async function () {
|
||||
if (process.env.SELENIUM_BROWSER === 'firefox') {
|
||||
this.skip()
|
||||
}
|
||||
|
||||
await driver.wait(async () => {
|
||||
const confirmedTxes = await findElements(driver, By.css('.transaction-list__completed-transactions .transaction-list-item'))
|
||||
return confirmedTxes.length === 3
|
||||
@ -1354,6 +1360,12 @@ describe('MetaMask', function () {
|
||||
})
|
||||
|
||||
describe('Tranfers a custom token from dapp when no gas value is specified', () => {
|
||||
before(function () {
|
||||
if (process.env.SELENIUM_BROWSER === 'firefox') {
|
||||
this.skip()
|
||||
}
|
||||
})
|
||||
|
||||
it('transfers an already created token, without specifying gas', async () => {
|
||||
const windowHandles = await driver.getAllWindowHandles()
|
||||
const extension = windowHandles[0]
|
||||
@ -1403,6 +1415,12 @@ describe('MetaMask', function () {
|
||||
})
|
||||
|
||||
describe('Approves a custom token from dapp when no gas value is specified', () => {
|
||||
before(function () {
|
||||
if (process.env.SELENIUM_BROWSER === 'firefox') {
|
||||
this.skip()
|
||||
}
|
||||
})
|
||||
|
||||
it('approves an already created token', async () => {
|
||||
const windowHandles = await driver.getAllWindowHandles()
|
||||
const extension = windowHandles[0]
|
||||
|
658
test/unit/app/controllers/incoming-transactions-test.js
Normal file
@ -0,0 +1,658 @@
|
||||
const assert = require('assert')
|
||||
const sinon = require('sinon')
|
||||
const proxyquire = require('proxyquire')
|
||||
const IncomingTransactionsController = proxyquire('../../../../app/scripts/controllers/incoming-transactions', {
|
||||
'../lib/random-id': () => 54321,
|
||||
})
|
||||
|
||||
const {
|
||||
ROPSTEN,
|
||||
RINKEBY,
|
||||
KOVAN,
|
||||
MAINNET,
|
||||
} = require('../../../../app/scripts/controllers/network/enums')
|
||||
|
||||
describe('IncomingTransactionsController', () => {
|
||||
const EMPTY_INIT_STATE = {
|
||||
incomingTransactions: {},
|
||||
incomingTxLastFetchedBlocksByNetwork: {
|
||||
[ROPSTEN]: null,
|
||||
[RINKEBY]: null,
|
||||
[KOVAN]: null,
|
||||
[MAINNET]: null,
|
||||
},
|
||||
}
|
||||
|
||||
const NON_EMPTY_INIT_STATE = {
|
||||
incomingTransactions: {
|
||||
'0x123456': { id: 777 },
|
||||
},
|
||||
incomingTxLastFetchedBlocksByNetwork: {
|
||||
[ROPSTEN]: 1,
|
||||
[RINKEBY]: 2,
|
||||
[KOVAN]: 3,
|
||||
[MAINNET]: 4,
|
||||
},
|
||||
}
|
||||
|
||||
const NON_EMPTY_INIT_STATE_WITH_FAKE_NETWORK_STATE = {
|
||||
incomingTransactions: {
|
||||
'0x123456': { id: 777 },
|
||||
},
|
||||
incomingTxLastFetchedBlocksByNetwork: {
|
||||
[ROPSTEN]: 1,
|
||||
[RINKEBY]: 2,
|
||||
[KOVAN]: 3,
|
||||
[MAINNET]: 4,
|
||||
FAKE_NETWORK: 1111,
|
||||
},
|
||||
}
|
||||
|
||||
const MOCK_BLOCKTRACKER = {
|
||||
addListener: sinon.spy(),
|
||||
removeListener: sinon.spy(),
|
||||
testProperty: 'fakeBlockTracker',
|
||||
getCurrentBlock: () => '0xa',
|
||||
}
|
||||
|
||||
const MOCK_NETWORK_CONTROLLER = {
|
||||
getProviderConfig: () => ({ type: 'FAKE_NETWORK' }),
|
||||
on: sinon.spy(),
|
||||
}
|
||||
|
||||
const MOCK_PREFERENCES_CONTROLLER = {
|
||||
getSelectedAddress: sinon.stub().returns('0x0101'),
|
||||
store: {
|
||||
getState: sinon.stub().returns({
|
||||
featureFlags: {
|
||||
showIncomingTransactions: true,
|
||||
},
|
||||
}),
|
||||
subscribe: sinon.spy(),
|
||||
},
|
||||
}
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should set up correct store, listeners and properties in the constructor', () => {
|
||||
const incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: MOCK_BLOCKTRACKER,
|
||||
networkController: MOCK_NETWORK_CONTROLLER,
|
||||
preferencesController: MOCK_PREFERENCES_CONTROLLER,
|
||||
initState: {},
|
||||
})
|
||||
sinon.spy(incomingTransactionsController, '_update')
|
||||
|
||||
assert.deepEqual(incomingTransactionsController.blockTracker, MOCK_BLOCKTRACKER)
|
||||
assert.deepEqual(incomingTransactionsController.networkController, MOCK_NETWORK_CONTROLLER)
|
||||
assert.equal(incomingTransactionsController.preferencesController, MOCK_PREFERENCES_CONTROLLER)
|
||||
assert.equal(incomingTransactionsController.getCurrentNetwork(), 'FAKE_NETWORK')
|
||||
|
||||
assert.deepEqual(incomingTransactionsController.store.getState(), EMPTY_INIT_STATE)
|
||||
|
||||
assert(incomingTransactionsController.networkController.on.calledOnce)
|
||||
assert.equal(incomingTransactionsController.networkController.on.getCall(0).args[0], 'networkDidChange')
|
||||
const networkControllerListenerCallback = incomingTransactionsController.networkController.on.getCall(0).args[1]
|
||||
assert.equal(incomingTransactionsController._update.callCount, 0)
|
||||
networkControllerListenerCallback('testNetworkType')
|
||||
assert.equal(incomingTransactionsController._update.callCount, 1)
|
||||
assert.deepEqual(incomingTransactionsController._update.getCall(0).args[0], {
|
||||
address: '0x0101',
|
||||
networkType: 'testNetworkType',
|
||||
})
|
||||
|
||||
incomingTransactionsController._update.resetHistory()
|
||||
})
|
||||
|
||||
it('should set the store to a provided initial state', () => {
|
||||
const incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: MOCK_BLOCKTRACKER,
|
||||
networkController: MOCK_NETWORK_CONTROLLER,
|
||||
preferencesController: MOCK_PREFERENCES_CONTROLLER,
|
||||
initState: NON_EMPTY_INIT_STATE,
|
||||
})
|
||||
|
||||
assert.deepEqual(incomingTransactionsController.store.getState(), NON_EMPTY_INIT_STATE)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#start', () => {
|
||||
it('should set up a listener for the latest block', () => {
|
||||
const incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: MOCK_BLOCKTRACKER,
|
||||
networkController: MOCK_NETWORK_CONTROLLER,
|
||||
preferencesController: MOCK_PREFERENCES_CONTROLLER,
|
||||
initState: {},
|
||||
})
|
||||
sinon.spy(incomingTransactionsController, '_update')
|
||||
|
||||
incomingTransactionsController.start()
|
||||
|
||||
assert(incomingTransactionsController.blockTracker.addListener.calledOnce)
|
||||
assert.equal(incomingTransactionsController.blockTracker.addListener.getCall(0).args[0], 'latest')
|
||||
const blockTrackerListenerCallback = incomingTransactionsController.blockTracker.addListener.getCall(0).args[1]
|
||||
assert.equal(incomingTransactionsController._update.callCount, 0)
|
||||
blockTrackerListenerCallback('0xabc')
|
||||
assert.equal(incomingTransactionsController._update.callCount, 1)
|
||||
assert.deepEqual(incomingTransactionsController._update.getCall(0).args[0], {
|
||||
address: '0x0101',
|
||||
newBlockNumberDec: 2748,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('_getDataForUpdate', () => {
|
||||
it('should call fetchAll with the correct params when passed a new block number and the current network has no stored block', async () => {
|
||||
const incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: MOCK_BLOCKTRACKER,
|
||||
networkController: MOCK_NETWORK_CONTROLLER,
|
||||
preferencesController: MOCK_PREFERENCES_CONTROLLER,
|
||||
initState: NON_EMPTY_INIT_STATE,
|
||||
})
|
||||
incomingTransactionsController._fetchAll = sinon.stub().returns({})
|
||||
|
||||
await incomingTransactionsController._getDataForUpdate({ address: 'fakeAddress', newBlockNumberDec: 999 })
|
||||
|
||||
assert(incomingTransactionsController._fetchAll.calledOnce)
|
||||
|
||||
assert.deepEqual(incomingTransactionsController._fetchAll.getCall(0).args, [
|
||||
'fakeAddress', 999, 'FAKE_NETWORK',
|
||||
])
|
||||
})
|
||||
|
||||
it('should call fetchAll with the correct params when passed a new block number but the current network has a stored block', async () => {
|
||||
const incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: MOCK_BLOCKTRACKER,
|
||||
networkController: MOCK_NETWORK_CONTROLLER,
|
||||
preferencesController: MOCK_PREFERENCES_CONTROLLER,
|
||||
initState: NON_EMPTY_INIT_STATE_WITH_FAKE_NETWORK_STATE,
|
||||
})
|
||||
incomingTransactionsController._fetchAll = sinon.stub().returns({})
|
||||
|
||||
await incomingTransactionsController._getDataForUpdate({ address: 'fakeAddress', newBlockNumberDec: 999 })
|
||||
|
||||
assert(incomingTransactionsController._fetchAll.calledOnce)
|
||||
|
||||
assert.deepEqual(incomingTransactionsController._fetchAll.getCall(0).args, [
|
||||
'fakeAddress', 1111, 'FAKE_NETWORK',
|
||||
])
|
||||
})
|
||||
|
||||
it('should call fetchAll with the correct params when passed a new network type but no block info exists', async () => {
|
||||
const incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: MOCK_BLOCKTRACKER,
|
||||
networkController: MOCK_NETWORK_CONTROLLER,
|
||||
preferencesController: MOCK_PREFERENCES_CONTROLLER,
|
||||
initState: NON_EMPTY_INIT_STATE_WITH_FAKE_NETWORK_STATE,
|
||||
})
|
||||
incomingTransactionsController._fetchAll = sinon.stub().returns({})
|
||||
|
||||
await incomingTransactionsController._getDataForUpdate({
|
||||
address: 'fakeAddress',
|
||||
networkType: 'NEW_FAKE_NETWORK',
|
||||
})
|
||||
|
||||
assert(incomingTransactionsController._fetchAll.calledOnce)
|
||||
|
||||
assert.deepEqual(incomingTransactionsController._fetchAll.getCall(0).args, [
|
||||
'fakeAddress', 10, 'NEW_FAKE_NETWORK',
|
||||
])
|
||||
})
|
||||
|
||||
it('should call fetchAll with the correct params when passed a new block number but the current network has a stored block', async () => {
|
||||
const incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: MOCK_BLOCKTRACKER,
|
||||
networkController: MOCK_NETWORK_CONTROLLER,
|
||||
preferencesController: MOCK_PREFERENCES_CONTROLLER,
|
||||
initState: NON_EMPTY_INIT_STATE_WITH_FAKE_NETWORK_STATE,
|
||||
})
|
||||
incomingTransactionsController._fetchAll = sinon.stub().returns({})
|
||||
|
||||
await incomingTransactionsController._getDataForUpdate({ address: 'fakeAddress', newBlockNumberDec: 999 })
|
||||
|
||||
assert(incomingTransactionsController._fetchAll.calledOnce)
|
||||
|
||||
assert.deepEqual(incomingTransactionsController._fetchAll.getCall(0).args, [
|
||||
'fakeAddress', 1111, 'FAKE_NETWORK',
|
||||
])
|
||||
})
|
||||
|
||||
it('should return the expected data', async () => {
|
||||
const incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: MOCK_BLOCKTRACKER,
|
||||
networkController: MOCK_NETWORK_CONTROLLER,
|
||||
preferencesController: MOCK_PREFERENCES_CONTROLLER,
|
||||
initState: NON_EMPTY_INIT_STATE_WITH_FAKE_NETWORK_STATE,
|
||||
})
|
||||
incomingTransactionsController._fetchAll = sinon.stub().returns({
|
||||
latestIncomingTxBlockNumber: 444,
|
||||
txs: [{ id: 555 }],
|
||||
})
|
||||
|
||||
const result = await incomingTransactionsController._getDataForUpdate({
|
||||
address: 'fakeAddress',
|
||||
networkType: 'FAKE_NETWORK',
|
||||
})
|
||||
|
||||
assert.deepEqual(result, {
|
||||
latestIncomingTxBlockNumber: 444,
|
||||
newTxs: [{ id: 555 }],
|
||||
currentIncomingTxs: {
|
||||
'0x123456': { id: 777 },
|
||||
},
|
||||
currentBlocksByNetwork: {
|
||||
[ROPSTEN]: 1,
|
||||
[RINKEBY]: 2,
|
||||
[KOVAN]: 3,
|
||||
[MAINNET]: 4,
|
||||
FAKE_NETWORK: 1111,
|
||||
},
|
||||
fetchedBlockNumber: 1111,
|
||||
network: 'FAKE_NETWORK',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('_updateStateWithNewTxData', () => {
|
||||
const MOCK_INPUT_WITHOUT_LASTEST = {
|
||||
newTxs: [{ id: 555, hash: '0xfff' }],
|
||||
currentIncomingTxs: {
|
||||
'0x123456': { id: 777, hash: '0x123456' },
|
||||
},
|
||||
currentBlocksByNetwork: {
|
||||
[ROPSTEN]: 1,
|
||||
[RINKEBY]: 2,
|
||||
[KOVAN]: 3,
|
||||
[MAINNET]: 4,
|
||||
FAKE_NETWORK: 1111,
|
||||
},
|
||||
fetchedBlockNumber: 1111,
|
||||
network: 'FAKE_NETWORK',
|
||||
}
|
||||
|
||||
const MOCK_INPUT_WITH_LASTEST = {
|
||||
...MOCK_INPUT_WITHOUT_LASTEST,
|
||||
latestIncomingTxBlockNumber: 444,
|
||||
}
|
||||
|
||||
it('should update state with correct blockhash and transactions when passed a truthy latestIncomingTxBlockNumber', async () => {
|
||||
const incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: MOCK_BLOCKTRACKER,
|
||||
networkController: MOCK_NETWORK_CONTROLLER,
|
||||
preferencesController: MOCK_PREFERENCES_CONTROLLER,
|
||||
initState: NON_EMPTY_INIT_STATE,
|
||||
})
|
||||
sinon.spy(incomingTransactionsController.store, 'updateState')
|
||||
|
||||
await incomingTransactionsController._updateStateWithNewTxData(MOCK_INPUT_WITH_LASTEST)
|
||||
|
||||
assert(incomingTransactionsController.store.updateState.calledOnce)
|
||||
|
||||
assert.deepEqual(incomingTransactionsController.store.updateState.getCall(0).args[0], {
|
||||
incomingTxLastFetchedBlocksByNetwork: {
|
||||
...MOCK_INPUT_WITH_LASTEST.currentBlocksByNetwork,
|
||||
'FAKE_NETWORK': 445,
|
||||
},
|
||||
incomingTransactions: {
|
||||
'0x123456': { id: 777, hash: '0x123456' },
|
||||
'0xfff': { id: 555, hash: '0xfff' },
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should update state with correct blockhash and transactions when passed a falsy latestIncomingTxBlockNumber', async () => {
|
||||
const incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: MOCK_BLOCKTRACKER,
|
||||
networkController: MOCK_NETWORK_CONTROLLER,
|
||||
preferencesController: MOCK_PREFERENCES_CONTROLLER,
|
||||
initState: NON_EMPTY_INIT_STATE,
|
||||
})
|
||||
sinon.spy(incomingTransactionsController.store, 'updateState')
|
||||
|
||||
await incomingTransactionsController._updateStateWithNewTxData(MOCK_INPUT_WITHOUT_LASTEST)
|
||||
|
||||
assert(incomingTransactionsController.store.updateState.calledOnce)
|
||||
|
||||
assert.deepEqual(incomingTransactionsController.store.updateState.getCall(0).args[0], {
|
||||
incomingTxLastFetchedBlocksByNetwork: {
|
||||
...MOCK_INPUT_WITH_LASTEST.currentBlocksByNetwork,
|
||||
'FAKE_NETWORK': 1112,
|
||||
},
|
||||
incomingTransactions: {
|
||||
'0x123456': { id: 777, hash: '0x123456' },
|
||||
'0xfff': { id: 555, hash: '0xfff' },
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('_fetchTxs', () => {
|
||||
const mockFetch = sinon.stub().returns(Promise.resolve({
|
||||
json: () => Promise.resolve({ someKey: 'someValue' }),
|
||||
}))
|
||||
let tempFetch
|
||||
beforeEach(() => {
|
||||
tempFetch = global.fetch
|
||||
global.fetch = mockFetch
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
global.fetch = tempFetch
|
||||
mockFetch.resetHistory()
|
||||
})
|
||||
|
||||
it('should call fetch with the expected url when passed an address, block number and supported network', async () => {
|
||||
const incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: MOCK_BLOCKTRACKER,
|
||||
networkController: MOCK_NETWORK_CONTROLLER,
|
||||
preferencesController: MOCK_PREFERENCES_CONTROLLER,
|
||||
initState: NON_EMPTY_INIT_STATE,
|
||||
})
|
||||
|
||||
await incomingTransactionsController._fetchTxs('0xfakeaddress', '789', ROPSTEN)
|
||||
|
||||
assert(mockFetch.calledOnce)
|
||||
assert.equal(mockFetch.getCall(0).args[0], `https://api-${ROPSTEN}.etherscan.io/api?module=account&action=txlist&address=0xfakeaddress&tag=latest&page=1&startBlock=789`)
|
||||
})
|
||||
|
||||
it('should call fetch with the expected url when passed an address, block number and MAINNET', async () => {
|
||||
const incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: MOCK_BLOCKTRACKER,
|
||||
networkController: MOCK_NETWORK_CONTROLLER,
|
||||
preferencesController: MOCK_PREFERENCES_CONTROLLER,
|
||||
initState: NON_EMPTY_INIT_STATE,
|
||||
})
|
||||
|
||||
await incomingTransactionsController._fetchTxs('0xfakeaddress', '789', MAINNET)
|
||||
|
||||
assert(mockFetch.calledOnce)
|
||||
assert.equal(mockFetch.getCall(0).args[0], `https://api.etherscan.io/api?module=account&action=txlist&address=0xfakeaddress&tag=latest&page=1&startBlock=789`)
|
||||
})
|
||||
|
||||
it('should call fetch with the expected url when passed an address and supported network, but a falsy block number', async () => {
|
||||
const incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: MOCK_BLOCKTRACKER,
|
||||
networkController: MOCK_NETWORK_CONTROLLER,
|
||||
preferencesController: MOCK_PREFERENCES_CONTROLLER,
|
||||
initState: NON_EMPTY_INIT_STATE,
|
||||
})
|
||||
|
||||
await incomingTransactionsController._fetchTxs('0xfakeaddress', null, ROPSTEN)
|
||||
|
||||
assert(mockFetch.calledOnce)
|
||||
assert.equal(mockFetch.getCall(0).args[0], `https://api-${ROPSTEN}.etherscan.io/api?module=account&action=txlist&address=0xfakeaddress&tag=latest&page=1`)
|
||||
})
|
||||
|
||||
it('should not fetch and return an empty object when passed an unsported network', async () => {
|
||||
const incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: MOCK_BLOCKTRACKER,
|
||||
networkController: MOCK_NETWORK_CONTROLLER,
|
||||
preferencesController: MOCK_PREFERENCES_CONTROLLER,
|
||||
initState: NON_EMPTY_INIT_STATE,
|
||||
})
|
||||
|
||||
const result = await incomingTransactionsController._fetchTxs('0xfakeaddress', null, 'UNSUPPORTED_NETWORK')
|
||||
|
||||
assert(mockFetch.notCalled)
|
||||
assert.deepEqual(result, {})
|
||||
})
|
||||
|
||||
it('should return the results from the fetch call, plus the address and currentNetworkID, when passed an address, block number and supported network', async () => {
|
||||
const incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: MOCK_BLOCKTRACKER,
|
||||
networkController: MOCK_NETWORK_CONTROLLER,
|
||||
preferencesController: MOCK_PREFERENCES_CONTROLLER,
|
||||
initState: NON_EMPTY_INIT_STATE,
|
||||
})
|
||||
|
||||
const result = await incomingTransactionsController._fetchTxs('0xfakeaddress', '789', ROPSTEN)
|
||||
|
||||
assert(mockFetch.calledOnce)
|
||||
assert.deepEqual(result, {
|
||||
someKey: 'someValue',
|
||||
address: '0xfakeaddress',
|
||||
currentNetworkID: 3,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('_processTxFetchResponse', () => {
|
||||
it('should return a null block number and empty tx array if status is 0', () => {
|
||||
const incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: MOCK_BLOCKTRACKER,
|
||||
networkController: MOCK_NETWORK_CONTROLLER,
|
||||
preferencesController: MOCK_PREFERENCES_CONTROLLER,
|
||||
initState: NON_EMPTY_INIT_STATE,
|
||||
})
|
||||
|
||||
const result = incomingTransactionsController._processTxFetchResponse({
|
||||
status: '0',
|
||||
result: [{ id: 1 }],
|
||||
address: '0xfakeaddress',
|
||||
})
|
||||
|
||||
assert.deepEqual(result, {
|
||||
latestIncomingTxBlockNumber: null,
|
||||
txs: [],
|
||||
})
|
||||
})
|
||||
|
||||
it('should return a null block number and empty tx array if the passed result array is empty', () => {
|
||||
const incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: MOCK_BLOCKTRACKER,
|
||||
networkController: MOCK_NETWORK_CONTROLLER,
|
||||
preferencesController: MOCK_PREFERENCES_CONTROLLER,
|
||||
initState: NON_EMPTY_INIT_STATE,
|
||||
})
|
||||
|
||||
const result = incomingTransactionsController._processTxFetchResponse({
|
||||
status: '1',
|
||||
result: [],
|
||||
address: '0xfakeaddress',
|
||||
})
|
||||
|
||||
assert.deepEqual(result, {
|
||||
latestIncomingTxBlockNumber: null,
|
||||
txs: [],
|
||||
})
|
||||
})
|
||||
|
||||
it('should return the expected block number and tx list when passed data from a successful fetch', () => {
|
||||
const incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: MOCK_BLOCKTRACKER,
|
||||
networkController: MOCK_NETWORK_CONTROLLER,
|
||||
preferencesController: MOCK_PREFERENCES_CONTROLLER,
|
||||
initState: NON_EMPTY_INIT_STATE,
|
||||
})
|
||||
|
||||
incomingTransactionsController._normalizeTxFromEtherscan = (tx, currentNetworkID) => ({
|
||||
...tx,
|
||||
currentNetworkID,
|
||||
normalized: true,
|
||||
})
|
||||
|
||||
const result = incomingTransactionsController._processTxFetchResponse({
|
||||
status: '1',
|
||||
address: '0xfakeaddress',
|
||||
currentNetworkID: 'FAKE_NETWORK',
|
||||
result: [
|
||||
{
|
||||
hash: '0xabc123',
|
||||
txParams: {
|
||||
to: '0xfakeaddress',
|
||||
},
|
||||
blockNumber: 5000,
|
||||
time: 10,
|
||||
},
|
||||
{
|
||||
hash: '0xabc123',
|
||||
txParams: {
|
||||
to: '0xfakeaddress',
|
||||
},
|
||||
blockNumber: 5000,
|
||||
time: 10,
|
||||
},
|
||||
{
|
||||
hash: '0xabc1234',
|
||||
txParams: {
|
||||
to: '0xfakeaddress',
|
||||
},
|
||||
blockNumber: 5000,
|
||||
time: 9,
|
||||
},
|
||||
{
|
||||
hash: '0xabc12345',
|
||||
txParams: {
|
||||
to: '0xfakeaddress',
|
||||
},
|
||||
blockNumber: 5001,
|
||||
time: 11,
|
||||
},
|
||||
{
|
||||
hash: '0xabc123456',
|
||||
txParams: {
|
||||
to: '0xfakeaddress',
|
||||
},
|
||||
blockNumber: 5001,
|
||||
time: 12,
|
||||
},
|
||||
{
|
||||
hash: '0xabc1234567',
|
||||
txParams: {
|
||||
to: '0xanotherFakeaddress',
|
||||
},
|
||||
blockNumber: 5002,
|
||||
time: 13,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
assert.deepEqual(result, {
|
||||
latestIncomingTxBlockNumber: 5001,
|
||||
txs: [
|
||||
{
|
||||
hash: '0xabc1234',
|
||||
txParams: {
|
||||
to: '0xfakeaddress',
|
||||
},
|
||||
blockNumber: 5000,
|
||||
time: 9,
|
||||
normalized: true,
|
||||
currentNetworkID: 'FAKE_NETWORK',
|
||||
},
|
||||
{
|
||||
hash: '0xabc123',
|
||||
txParams: {
|
||||
to: '0xfakeaddress',
|
||||
},
|
||||
blockNumber: 5000,
|
||||
time: 10,
|
||||
normalized: true,
|
||||
currentNetworkID: 'FAKE_NETWORK',
|
||||
},
|
||||
{
|
||||
hash: '0xabc12345',
|
||||
txParams: {
|
||||
to: '0xfakeaddress',
|
||||
},
|
||||
blockNumber: 5001,
|
||||
time: 11,
|
||||
normalized: true,
|
||||
currentNetworkID: 'FAKE_NETWORK',
|
||||
},
|
||||
{
|
||||
hash: '0xabc123456',
|
||||
txParams: {
|
||||
to: '0xfakeaddress',
|
||||
},
|
||||
blockNumber: 5001,
|
||||
time: 12,
|
||||
normalized: true,
|
||||
currentNetworkID: 'FAKE_NETWORK',
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('_normalizeTxFromEtherscan', () => {
|
||||
it('should return the expected data when the tx is in error', () => {
|
||||
const incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: MOCK_BLOCKTRACKER,
|
||||
networkController: MOCK_NETWORK_CONTROLLER,
|
||||
preferencesController: MOCK_PREFERENCES_CONTROLLER,
|
||||
initState: NON_EMPTY_INIT_STATE,
|
||||
})
|
||||
|
||||
const result = incomingTransactionsController._normalizeTxFromEtherscan({
|
||||
timeStamp: '4444',
|
||||
isError: '1',
|
||||
blockNumber: 333,
|
||||
from: '0xa',
|
||||
gas: '11',
|
||||
gasPrice: '12',
|
||||
nonce: '13',
|
||||
to: '0xe',
|
||||
value: '15',
|
||||
hash: '0xg',
|
||||
}, 'FAKE_NETWORK')
|
||||
|
||||
assert.deepEqual(result, {
|
||||
blockNumber: 333,
|
||||
id: 54321,
|
||||
metamaskNetworkId: 'FAKE_NETWORK',
|
||||
status: 'failed',
|
||||
time: 4444000,
|
||||
txParams: {
|
||||
from: '0xa',
|
||||
gas: '0xb',
|
||||
gasPrice: '0xc',
|
||||
nonce: '0xd',
|
||||
to: '0xe',
|
||||
value: '0xf',
|
||||
},
|
||||
hash: '0xg',
|
||||
transactionCategory: 'incoming',
|
||||
})
|
||||
})
|
||||
|
||||
it('should return the expected data when the tx is not in error', () => {
|
||||
const incomingTransactionsController = new IncomingTransactionsController({
|
||||
blockTracker: MOCK_BLOCKTRACKER,
|
||||
networkController: MOCK_NETWORK_CONTROLLER,
|
||||
preferencesController: MOCK_PREFERENCES_CONTROLLER,
|
||||
initState: NON_EMPTY_INIT_STATE,
|
||||
})
|
||||
|
||||
const result = incomingTransactionsController._normalizeTxFromEtherscan({
|
||||
timeStamp: '4444',
|
||||
isError: '0',
|
||||
blockNumber: 333,
|
||||
from: '0xa',
|
||||
gas: '11',
|
||||
gasPrice: '12',
|
||||
nonce: '13',
|
||||
to: '0xe',
|
||||
value: '15',
|
||||
hash: '0xg',
|
||||
}, 'FAKE_NETWORK')
|
||||
|
||||
assert.deepEqual(result, {
|
||||
blockNumber: 333,
|
||||
id: 54321,
|
||||
metamaskNetworkId: 'FAKE_NETWORK',
|
||||
status: 'confirmed',
|
||||
time: 4444000,
|
||||
txParams: {
|
||||
from: '0xa',
|
||||
gas: '0xb',
|
||||
gasPrice: '0xc',
|
||||
nonce: '0xd',
|
||||
to: '0xe',
|
||||
value: '0xf',
|
||||
},
|
||||
hash: '0xg',
|
||||
transactionCategory: 'incoming',
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
@ -758,14 +758,6 @@ describe('MetaMaskController', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('#markAccountsFound', function () {
|
||||
it('adds lost accounts to config manager data', function () {
|
||||
metamaskController.markAccountsFound(noop)
|
||||
const state = metamaskController.getState()
|
||||
assert.deepEqual(state.lostAccounts, [])
|
||||
})
|
||||
})
|
||||
|
||||
describe('#markPasswordForgotten', function () {
|
||||
it('adds and sets forgottenPassword to config data to true', function () {
|
||||
metamaskController.markPasswordForgotten(noop)
|
||||
|
260
test/unit/app/controllers/provider-approval-test.js
Normal file
@ -0,0 +1,260 @@
|
||||
const assert = require('assert')
|
||||
const sinon = require('sinon')
|
||||
const ProviderApprovalController = require('../../../../app/scripts/controllers/provider-approval')
|
||||
|
||||
const mockLockedKeyringController = {
|
||||
memStore: {
|
||||
getState: () => ({
|
||||
isUnlocked: false,
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
const mockUnlockedKeyringController = {
|
||||
memStore: {
|
||||
getState: () => ({
|
||||
isUnlocked: true,
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
describe('ProviderApprovalController', () => {
|
||||
describe('#_handleProviderRequest', () => {
|
||||
it('should add a pending provider request when unlocked', () => {
|
||||
const controller = new ProviderApprovalController({
|
||||
keyringController: mockUnlockedKeyringController,
|
||||
})
|
||||
|
||||
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg')
|
||||
assert.deepEqual(controller._getMergedState(), {
|
||||
approvedOrigins: {},
|
||||
providerRequests: [{
|
||||
origin: 'example.com',
|
||||
siteTitle: 'Example',
|
||||
siteImage: 'https://example.com/logo.svg',
|
||||
}],
|
||||
})
|
||||
})
|
||||
|
||||
it('should add a pending provider request when locked', () => {
|
||||
const controller = new ProviderApprovalController({
|
||||
keyringController: mockLockedKeyringController,
|
||||
})
|
||||
|
||||
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg')
|
||||
assert.deepEqual(controller._getMergedState(), {
|
||||
approvedOrigins: {},
|
||||
providerRequests: [{
|
||||
origin: 'example.com',
|
||||
siteTitle: 'Example',
|
||||
siteImage: 'https://example.com/logo.svg',
|
||||
}],
|
||||
})
|
||||
})
|
||||
|
||||
it('should add a 2nd pending provider request when unlocked', () => {
|
||||
const controller = new ProviderApprovalController({
|
||||
keyringController: mockUnlockedKeyringController,
|
||||
})
|
||||
|
||||
controller._handleProviderRequest('example1.com', 'Example 1', 'https://example1.com/logo.svg')
|
||||
controller._handleProviderRequest('example2.com', 'Example 2', 'https://example2.com/logo.svg')
|
||||
assert.deepEqual(controller._getMergedState(), {
|
||||
approvedOrigins: {},
|
||||
providerRequests: [{
|
||||
origin: 'example1.com',
|
||||
siteTitle: 'Example 1',
|
||||
siteImage: 'https://example1.com/logo.svg',
|
||||
}, {
|
||||
origin: 'example2.com',
|
||||
siteTitle: 'Example 2',
|
||||
siteImage: 'https://example2.com/logo.svg',
|
||||
}],
|
||||
})
|
||||
})
|
||||
|
||||
it('should add a 2nd pending provider request when locked', () => {
|
||||
const controller = new ProviderApprovalController({
|
||||
keyringController: mockLockedKeyringController,
|
||||
})
|
||||
|
||||
controller._handleProviderRequest('example1.com', 'Example 1', 'https://example1.com/logo.svg')
|
||||
controller._handleProviderRequest('example2.com', 'Example 2', 'https://example2.com/logo.svg')
|
||||
assert.deepEqual(controller._getMergedState(), {
|
||||
approvedOrigins: {},
|
||||
providerRequests: [{
|
||||
origin: 'example1.com',
|
||||
siteTitle: 'Example 1',
|
||||
siteImage: 'https://example1.com/logo.svg',
|
||||
}, {
|
||||
origin: 'example2.com',
|
||||
siteTitle: 'Example 2',
|
||||
siteImage: 'https://example2.com/logo.svg',
|
||||
}],
|
||||
})
|
||||
})
|
||||
|
||||
it('should call openPopup when unlocked and when given', () => {
|
||||
const openPopup = sinon.spy()
|
||||
const controller = new ProviderApprovalController({
|
||||
openPopup,
|
||||
keyringController: mockUnlockedKeyringController,
|
||||
})
|
||||
|
||||
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg')
|
||||
assert.ok(openPopup.calledOnce)
|
||||
})
|
||||
|
||||
it('should call openPopup when locked and when given', () => {
|
||||
const openPopup = sinon.spy()
|
||||
const controller = new ProviderApprovalController({
|
||||
openPopup,
|
||||
keyringController: mockLockedKeyringController,
|
||||
})
|
||||
|
||||
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg')
|
||||
assert.ok(openPopup.calledOnce)
|
||||
})
|
||||
|
||||
it('should NOT call openPopup when unlocked and when the domain has already been approved', () => {
|
||||
const openPopup = sinon.spy()
|
||||
const controller = new ProviderApprovalController({
|
||||
openPopup,
|
||||
keyringController: mockUnlockedKeyringController,
|
||||
})
|
||||
|
||||
controller.store.updateState({
|
||||
approvedOrigins: {
|
||||
'example.com': {
|
||||
siteTitle: 'Example',
|
||||
siteImage: 'https://example.com/logo.svg',
|
||||
},
|
||||
},
|
||||
})
|
||||
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg')
|
||||
assert.ok(openPopup.notCalled)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#approveProviderRequestByOrigin', () => {
|
||||
it('should mark the origin as approved and remove the provider request', () => {
|
||||
const controller = new ProviderApprovalController({
|
||||
keyringController: mockUnlockedKeyringController,
|
||||
})
|
||||
|
||||
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg')
|
||||
controller.approveProviderRequestByOrigin('example.com')
|
||||
assert.deepEqual(controller._getMergedState(), {
|
||||
providerRequests: [],
|
||||
approvedOrigins: {
|
||||
'example.com': {
|
||||
siteTitle: 'Example',
|
||||
siteImage: 'https://example.com/logo.svg',
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should mark the origin as approved and multiple requests for the same domain', () => {
|
||||
const controller = new ProviderApprovalController({
|
||||
keyringController: mockUnlockedKeyringController,
|
||||
})
|
||||
|
||||
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg')
|
||||
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg')
|
||||
controller.approveProviderRequestByOrigin('example.com')
|
||||
assert.deepEqual(controller._getMergedState(), {
|
||||
providerRequests: [],
|
||||
approvedOrigins: {
|
||||
'example.com': {
|
||||
siteTitle: 'Example',
|
||||
siteImage: 'https://example.com/logo.svg',
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should mark the origin as approved without a provider request', () => {
|
||||
const controller = new ProviderApprovalController({
|
||||
keyringController: mockUnlockedKeyringController,
|
||||
})
|
||||
|
||||
controller.approveProviderRequestByOrigin('example.com')
|
||||
assert.deepEqual(controller._getMergedState(), {
|
||||
providerRequests: [],
|
||||
approvedOrigins: {
|
||||
'example.com': {
|
||||
siteTitle: null,
|
||||
siteImage: null,
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('#rejectProviderRequestByOrigin', () => {
|
||||
it('should remove the origin from approved', () => {
|
||||
const controller = new ProviderApprovalController({
|
||||
keyringController: mockUnlockedKeyringController,
|
||||
})
|
||||
|
||||
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg')
|
||||
controller.approveProviderRequestByOrigin('example.com')
|
||||
controller.rejectProviderRequestByOrigin('example.com')
|
||||
assert.deepEqual(controller._getMergedState(), {
|
||||
providerRequests: [],
|
||||
approvedOrigins: {},
|
||||
})
|
||||
})
|
||||
|
||||
it('should reject the origin even without a pending request', () => {
|
||||
const controller = new ProviderApprovalController({
|
||||
keyringController: mockUnlockedKeyringController,
|
||||
})
|
||||
|
||||
controller.rejectProviderRequestByOrigin('example.com')
|
||||
assert.deepEqual(controller._getMergedState(), {
|
||||
providerRequests: [],
|
||||
approvedOrigins: {},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('#clearApprovedOrigins', () => {
|
||||
it('should clear the approved origins', () => {
|
||||
const controller = new ProviderApprovalController({
|
||||
keyringController: mockUnlockedKeyringController,
|
||||
})
|
||||
|
||||
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg')
|
||||
controller.approveProviderRequestByOrigin('example.com')
|
||||
controller.clearApprovedOrigins()
|
||||
assert.deepEqual(controller._getMergedState(), {
|
||||
providerRequests: [],
|
||||
approvedOrigins: {},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('#shouldExposeAccounts', () => {
|
||||
it('should return true for an approved origin', () => {
|
||||
const controller = new ProviderApprovalController({
|
||||
keyringController: mockUnlockedKeyringController,
|
||||
})
|
||||
|
||||
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg')
|
||||
controller.approveProviderRequestByOrigin('example.com')
|
||||
assert.ok(controller.shouldExposeAccounts('example.com'))
|
||||
})
|
||||
|
||||
it('should return false for an origin not yet approved', () => {
|
||||
const controller = new ProviderApprovalController({
|
||||
keyringController: mockUnlockedKeyringController,
|
||||
})
|
||||
|
||||
controller._handleProviderRequest('example.com', 'Example', 'https://example.com/logo.svg')
|
||||
controller.approveProviderRequestByOrigin('example.com')
|
||||
assert.ok(!controller.shouldExposeAccounts('bad.website'))
|
||||
})
|
||||
})
|
||||
})
|
119
test/unit/migrations/036-test.js
Normal file
@ -0,0 +1,119 @@
|
||||
const assert = require('assert')
|
||||
const migration36 = require('../../../app/scripts/migrations/036')
|
||||
|
||||
describe('migration #36', () => {
|
||||
it('should update the version metadata', (done) => {
|
||||
const oldStorage = {
|
||||
'meta': {
|
||||
'version': 35,
|
||||
},
|
||||
'data': {},
|
||||
}
|
||||
|
||||
migration36.migrate(oldStorage)
|
||||
.then((newStorage) => {
|
||||
assert.deepEqual(newStorage.meta, {
|
||||
'version': 36,
|
||||
})
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
})
|
||||
|
||||
it('should remove privacyMode if featureFlags.privacyMode was false', (done) => {
|
||||
const oldStorage = {
|
||||
'meta': {},
|
||||
'data': {
|
||||
'PreferencesController': {
|
||||
'featureFlags': {
|
||||
'privacyMode': false,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
migration36.migrate(oldStorage)
|
||||
.then((newStorage) => {
|
||||
assert.deepEqual(newStorage.data.PreferencesController, {
|
||||
'featureFlags': {
|
||||
},
|
||||
})
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
})
|
||||
|
||||
it('should remove privacyMode if featureFlags.privacyMode was true', (done) => {
|
||||
const oldStorage = {
|
||||
'meta': {},
|
||||
'data': {
|
||||
'PreferencesController': {
|
||||
'featureFlags': {
|
||||
'privacyMode': true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
migration36.migrate(oldStorage)
|
||||
.then((newStorage) => {
|
||||
assert.deepEqual(newStorage.data.PreferencesController, {
|
||||
'featureFlags': {
|
||||
},
|
||||
})
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
})
|
||||
|
||||
it('should NOT change any state if privacyMode does not exist', (done) => {
|
||||
const oldStorage = {
|
||||
'meta': {},
|
||||
'data': {
|
||||
'PreferencesController': {
|
||||
'migratedPrivacyMode': true,
|
||||
'featureFlags': {
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
migration36.migrate(oldStorage)
|
||||
.then((newStorage) => {
|
||||
assert.deepEqual(newStorage.data, oldStorage.data)
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
})
|
||||
|
||||
it('should NOT change any state if PreferencesController is missing', (done) => {
|
||||
const oldStorage = {
|
||||
'meta': {},
|
||||
'data': {},
|
||||
}
|
||||
|
||||
migration36.migrate(oldStorage)
|
||||
.then((newStorage) => {
|
||||
assert.deepEqual(newStorage.data, oldStorage.data)
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
})
|
||||
|
||||
it('should NOT change any state if featureFlags is missing', (done) => {
|
||||
const oldStorage = {
|
||||
'meta': {},
|
||||
'data': {
|
||||
'PreferencesController': {
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
migration36.migrate(oldStorage)
|
||||
.then((newStorage) => {
|
||||
assert.deepEqual(newStorage.data, oldStorage.data)
|
||||
done()
|
||||
})
|
||||
.catch(done)
|
||||
})
|
||||
})
|
@ -24,6 +24,7 @@ export default class ConfirmPageContainer extends Component {
|
||||
fromName: PropTypes.string,
|
||||
toAddress: PropTypes.string,
|
||||
toName: PropTypes.string,
|
||||
toNickname: PropTypes.string,
|
||||
// Content
|
||||
contentComponent: PropTypes.node,
|
||||
errorKey: PropTypes.string,
|
||||
@ -68,6 +69,7 @@ export default class ConfirmPageContainer extends Component {
|
||||
fromName,
|
||||
fromAddress,
|
||||
toName,
|
||||
toNickname,
|
||||
toAddress,
|
||||
disabled,
|
||||
errorKey,
|
||||
@ -126,6 +128,7 @@ export default class ConfirmPageContainer extends Component {
|
||||
senderAddress={fromAddress}
|
||||
recipientName={toName}
|
||||
recipientAddress={toAddress}
|
||||
recipientNickname={toNickname}
|
||||
assetImage={renderAssetImage ? assetImage : undefined}
|
||||
/>
|
||||
</ConfirmPageContainerHeader>
|
||||
|
@ -215,7 +215,7 @@ SignatureRequest.prototype.msgHexToText = function (hex) {
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/display-name
|
||||
SignatureRequest.prototype.renderTypedDataV3 = function (data) {
|
||||
SignatureRequest.prototype.renderTypedData = function (data) {
|
||||
const { domain, message } = JSON.parse(data)
|
||||
return [
|
||||
h('div.request-signature__typed-container', [
|
||||
@ -267,17 +267,18 @@ SignatureRequest.prototype.renderBody = function () {
|
||||
}),
|
||||
}, [notice]),
|
||||
|
||||
h('div.request-signature__rows', type === 'eth_signTypedData' && version === 'V3' ?
|
||||
this.renderTypedDataV3(data) :
|
||||
rows.map(({ name, value }) => {
|
||||
if (typeof value === 'boolean') {
|
||||
value = value.toString()
|
||||
}
|
||||
return h('div.request-signature__row', [
|
||||
h('div.request-signature__row-title', [`${name}:`]),
|
||||
h('div.request-signature__row-value', value),
|
||||
])
|
||||
}),
|
||||
h('div.request-signature__rows',
|
||||
type === 'eth_signTypedData' && (version === 'V3' || version === 'V4') ?
|
||||
this.renderTypedData(data) :
|
||||
rows.map(({ name, value }) => {
|
||||
if (typeof value === 'boolean') {
|
||||
value = value.toString()
|
||||
}
|
||||
return h('div.request-signature__row', [
|
||||
h('div.request-signature__row-title', [`${name}:`]),
|
||||
h('div.request-signature__row-value', value),
|
||||
])
|
||||
}),
|
||||
),
|
||||
])
|
||||
}
|
||||
|
@ -12,6 +12,9 @@
|
||||
}
|
||||
|
||||
&__value {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
text-align: end;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
|
@ -36,6 +36,7 @@ export default class TransactionListItem extends PureComponent {
|
||||
rpcPrefs: PropTypes.object,
|
||||
data: PropTypes.string,
|
||||
getContractMethodData: PropTypes.func,
|
||||
isDeposit: PropTypes.bool,
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
@ -117,7 +118,7 @@ export default class TransactionListItem extends PureComponent {
|
||||
}
|
||||
|
||||
renderPrimaryCurrency () {
|
||||
const { token, primaryTransaction: { txParams: { data } = {} } = {}, value } = this.props
|
||||
const { token, primaryTransaction: { txParams: { data } = {} } = {}, value, isDeposit } = this.props
|
||||
|
||||
return token
|
||||
? (
|
||||
@ -132,7 +133,7 @@ export default class TransactionListItem extends PureComponent {
|
||||
className="transaction-list-item__amount transaction-list-item__amount--primary"
|
||||
value={value}
|
||||
type={PRIMARY}
|
||||
prefix="-"
|
||||
prefix={isDeposit ? '' : '-'}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -21,8 +21,10 @@ const mapStateToProps = (state, ownProps) => {
|
||||
const { showFiatInTestnets } = preferencesSelector(state)
|
||||
const isMainnet = getIsMainnet(state)
|
||||
const { transactionGroup: { primaryTransaction } = {} } = ownProps
|
||||
const { txParams: { gas: gasLimit, gasPrice, data } = {} } = primaryTransaction
|
||||
const selectedAccountBalance = accounts[getSelectedAddress(state)].balance
|
||||
const { txParams: { gas: gasLimit, gasPrice, data, to } = {} } = primaryTransaction
|
||||
const selectedAddress = getSelectedAddress(state)
|
||||
const selectedAccountBalance = accounts[selectedAddress].balance
|
||||
const isDeposit = selectedAddress === to
|
||||
const selectRpcInfo = frequentRpcListDetail.find(rpcInfo => rpcInfo.rpcUrl === provider.rpcTarget)
|
||||
const { rpcPrefs } = selectRpcInfo || {}
|
||||
|
||||
@ -42,6 +44,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
selectedAccountBalance,
|
||||
hasEnoughCancelGas,
|
||||
rpcPrefs,
|
||||
isDeposit,
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,12 +71,13 @@ const mapDispatchToProps = dispatch => {
|
||||
|
||||
const mergeProps = (stateProps, dispatchProps, ownProps) => {
|
||||
const { transactionGroup: { primaryTransaction, initialTransaction } = {} } = ownProps
|
||||
const { isDeposit } = stateProps
|
||||
const { retryTransaction, ...restDispatchProps } = dispatchProps
|
||||
const { txParams: { nonce, data } = {}, time } = initialTransaction
|
||||
const { txParams: { nonce, data } = {}, time = 0 } = initialTransaction
|
||||
const { txParams: { value } = {} } = primaryTransaction
|
||||
|
||||
const tokenData = data && getTokenData(data)
|
||||
const nonceAndDate = nonce ? `#${hexToDecimal(nonce)} - ${formatDate(time)}` : formatDate(time)
|
||||
const nonceAndDate = nonce && !isDeposit ? `#${hexToDecimal(nonce)} - ${formatDate(time)}` : formatDate(time)
|
||||
|
||||
return {
|
||||
...stateProps,
|
||||
|
@ -19,6 +19,7 @@ export default class SenderToRecipient extends PureComponent {
|
||||
senderAddress: PropTypes.string,
|
||||
recipientName: PropTypes.string,
|
||||
recipientAddress: PropTypes.string,
|
||||
recipientNickname: PropTypes.string,
|
||||
t: PropTypes.func,
|
||||
variant: PropTypes.oneOf([DEFAULT_VARIANT, CARDS_VARIANT, FLAT_VARIANT]),
|
||||
addressOnly: PropTypes.bool,
|
||||
@ -88,7 +89,7 @@ export default class SenderToRecipient extends PureComponent {
|
||||
|
||||
renderRecipientWithAddress () {
|
||||
const { t } = this.context
|
||||
const { recipientName, recipientAddress, addressOnly, onRecipientClick } = this.props
|
||||
const { recipientName, recipientAddress, recipientNickname, addressOnly, onRecipientClick } = this.props
|
||||
const checksummedRecipientAddress = checksumAddress(recipientAddress)
|
||||
|
||||
return (
|
||||
@ -114,7 +115,7 @@ export default class SenderToRecipient extends PureComponent {
|
||||
{
|
||||
addressOnly
|
||||
? `${t('to')}: ${checksummedRecipientAddress}`
|
||||
: (recipientName || this.context.t('newContract'))
|
||||
: (recipientNickname || recipientName || this.context.t('newContract'))
|
||||
}
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
@ -3,6 +3,7 @@ const UNLOCK_ROUTE = '/unlock'
|
||||
const LOCK_ROUTE = '/lock'
|
||||
const SETTINGS_ROUTE = '/settings'
|
||||
const GENERAL_ROUTE = '/settings/general'
|
||||
const CONNECTIONS_ROUTE = '/settings/connections'
|
||||
const ADVANCED_ROUTE = '/settings/advanced'
|
||||
const SECURITY_ROUTE = '/settings/security'
|
||||
const ABOUT_US_ROUTE = '/settings/about-us'
|
||||
@ -82,6 +83,7 @@ module.exports = {
|
||||
ADVANCED_ROUTE,
|
||||
SECURITY_ROUTE,
|
||||
GENERAL_ROUTE,
|
||||
CONNECTIONS_ROUTE,
|
||||
ABOUT_US_ROUTE,
|
||||
CONTACT_LIST_ROUTE,
|
||||
CONTACT_EDIT_ROUTE,
|
||||
@ -93,4 +95,3 @@ module.exports = {
|
||||
NETWORKS_ROUTE,
|
||||
INITIALIZE_BACKUP_SEED_PHRASE_ROUTE,
|
||||
}
|
||||
|
||||
|
@ -20,5 +20,6 @@ export const TRANSFER_FROM_ACTION_KEY = 'transferFrom'
|
||||
export const SIGNATURE_REQUEST_KEY = 'signatureRequest'
|
||||
export const CONTRACT_INTERACTION_KEY = 'contractInteraction'
|
||||
export const CANCEL_ATTEMPT_ACTION_KEY = 'cancelAttempt'
|
||||
export const DEPOSIT_TRANSACTION_KEY = 'deposit'
|
||||
|
||||
export const TRANSACTION_TYPE_SHAPESHIFT = 'shapeshift'
|
||||
|
@ -21,6 +21,7 @@ import {
|
||||
SIGNATURE_REQUEST_KEY,
|
||||
CONTRACT_INTERACTION_KEY,
|
||||
CANCEL_ATTEMPT_ACTION_KEY,
|
||||
DEPOSIT_TRANSACTION_KEY,
|
||||
} from '../constants/transactions'
|
||||
|
||||
import log from 'loglevel'
|
||||
@ -124,6 +125,10 @@ export function isTokenMethodAction (transactionCategory) {
|
||||
export function getTransactionActionKey (transaction) {
|
||||
const { msgParams, type, transactionCategory } = transaction
|
||||
|
||||
if (transactionCategory === 'incoming') {
|
||||
return DEPOSIT_TRANSACTION_KEY
|
||||
}
|
||||
|
||||
if (type === 'cancel') {
|
||||
return CANCEL_ATTEMPT_ACTION_KEY
|
||||
}
|
||||
|
@ -59,6 +59,7 @@ export default class ConfirmTransactionBase extends Component {
|
||||
tokenData: PropTypes.object,
|
||||
tokenProps: PropTypes.object,
|
||||
toName: PropTypes.string,
|
||||
toNickname: PropTypes.string,
|
||||
transactionStatus: PropTypes.string,
|
||||
txData: PropTypes.object,
|
||||
unapprovedTxCount: PropTypes.number,
|
||||
@ -529,6 +530,7 @@ export default class ConfirmTransactionBase extends Component {
|
||||
fromAddress,
|
||||
toName,
|
||||
toAddress,
|
||||
toNickname,
|
||||
methodData,
|
||||
valid: propsValid = true,
|
||||
errorMessage,
|
||||
@ -551,13 +553,13 @@ export default class ConfirmTransactionBase extends Component {
|
||||
const { name } = methodData
|
||||
const { valid, errorKey } = this.getErrorKey()
|
||||
const { totalTx, positionOfCurrentTx, nextTxId, prevTxId, showNavigation, firstTx, lastTx, ofText, requestsWaitingText } = this.getNavigateTxData()
|
||||
|
||||
return (
|
||||
<ConfirmPageContainer
|
||||
fromName={fromName}
|
||||
fromAddress={fromAddress}
|
||||
toName={toName}
|
||||
toAddress={toAddress}
|
||||
toNickname={toNickname}
|
||||
showEdit={onEdit && !isTxReprice}
|
||||
// In the event that the key is falsy (and inherently invalid), use a fallback string
|
||||
action={getMethodName(name) || this.context.tOrKey(transactionCategory) || this.context.t('contractInteraction')}
|
||||
|
@ -37,6 +37,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
const {
|
||||
conversionRate,
|
||||
identities,
|
||||
addressBook,
|
||||
currentCurrency,
|
||||
selectedAddress,
|
||||
selectedAddressTxList,
|
||||
@ -75,6 +76,8 @@ const mapStateToProps = (state, ownProps) => {
|
||||
: addressSlicer(checksumAddress(toAddress))
|
||||
)
|
||||
|
||||
const addressBookObject = addressBook[checksumAddress(toAddress)]
|
||||
const toNickname = addressBookObject ? addressBookObject.name : ''
|
||||
const isTxReprice = Boolean(lastGasPrice)
|
||||
const transactionStatus = transaction ? transaction.status : ''
|
||||
|
||||
@ -115,6 +118,7 @@ const mapStateToProps = (state, ownProps) => {
|
||||
fromName,
|
||||
toAddress,
|
||||
toName,
|
||||
toNickname,
|
||||
ethTransactionAmount,
|
||||
ethTransactionFee,
|
||||
ethTransactionTotal,
|
||||
|
@ -49,7 +49,7 @@ export default class EndOfFlowScreen extends PureComponent {
|
||||
{ '• ' + t('endOfFlowMessage7') }
|
||||
</div>
|
||||
<div className="first-time-flow__text-block end-of-flow__text-4">
|
||||
*MetaMask cannot recover your seedphrase. <a
|
||||
{ '*' + t('endOfFlowMessage8') } <a
|
||||
href="https://metamask.zendesk.com/hc/en-us/articles/360015489591-Basic-Safety-Tips"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
|
@ -21,18 +21,10 @@ export default class Home extends PureComponent {
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
activeTab: {},
|
||||
unsetMigratedPrivacyMode: null,
|
||||
forceApproveProviderRequestByOrigin: null,
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
activeTab: PropTypes.shape({
|
||||
origin: PropTypes.string,
|
||||
protocol: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
url: PropTypes.string,
|
||||
}),
|
||||
history: PropTypes.object,
|
||||
forgottenPassword: PropTypes.bool,
|
||||
suggestedTokens: PropTypes.object,
|
||||
@ -40,10 +32,7 @@ export default class Home extends PureComponent {
|
||||
providerRequests: PropTypes.array,
|
||||
showPrivacyModeNotification: PropTypes.bool.isRequired,
|
||||
unsetMigratedPrivacyMode: PropTypes.func,
|
||||
viewingUnconnectedDapp: PropTypes.bool.isRequired,
|
||||
forceApproveProviderRequestByOrigin: PropTypes.func,
|
||||
shouldShowSeedPhraseReminder: PropTypes.bool,
|
||||
rejectProviderRequestByOrigin: PropTypes.func,
|
||||
isPopup: PropTypes.bool,
|
||||
}
|
||||
|
||||
@ -73,16 +62,12 @@ export default class Home extends PureComponent {
|
||||
render () {
|
||||
const { t } = this.context
|
||||
const {
|
||||
activeTab,
|
||||
forgottenPassword,
|
||||
providerRequests,
|
||||
history,
|
||||
showPrivacyModeNotification,
|
||||
unsetMigratedPrivacyMode,
|
||||
viewingUnconnectedDapp,
|
||||
forceApproveProviderRequestByOrigin,
|
||||
shouldShowSeedPhraseReminder,
|
||||
rejectProviderRequestByOrigin,
|
||||
isPopup,
|
||||
} = this.props
|
||||
|
||||
@ -120,20 +105,6 @@ export default class Home extends PureComponent {
|
||||
key="home-privacyModeDefault"
|
||||
/>,
|
||||
},
|
||||
{
|
||||
shouldBeRendered: viewingUnconnectedDapp,
|
||||
component: <HomeNotification
|
||||
descriptionText={t('shareAddressToConnect', [activeTab.origin])}
|
||||
acceptText={t('shareAddress')}
|
||||
onAccept={() => {
|
||||
forceApproveProviderRequestByOrigin(activeTab.origin)
|
||||
}}
|
||||
ignoreText={t('dismiss')}
|
||||
onIgnore={() => rejectProviderRequestByOrigin(activeTab.origin)}
|
||||
infoText={t('shareAddressInfo', [activeTab.origin])}
|
||||
key="home-shareAddressToConnect"
|
||||
/>,
|
||||
},
|
||||
{
|
||||
shouldBeRendered: shouldShowSeedPhraseReminder,
|
||||
component: <HomeNotification
|
||||
|
@ -5,51 +5,31 @@ import { withRouter } from 'react-router-dom'
|
||||
import { unconfirmedTransactionsCountSelector } from '../../selectors/confirm-transaction'
|
||||
import { getCurrentEthBalance } from '../../selectors/selectors'
|
||||
import {
|
||||
forceApproveProviderRequestByOrigin,
|
||||
unsetMigratedPrivacyMode,
|
||||
rejectProviderRequestByOrigin,
|
||||
} from '../../store/actions'
|
||||
import { getEnvironmentType } from '../../../../app/scripts/lib/util'
|
||||
import { ENVIRONMENT_TYPE_POPUP } from '../../../../app/scripts/lib/enums'
|
||||
|
||||
const activeTabDappProtocols = ['http:', 'https:', 'dweb:', 'ipfs:', 'ipns:', 'ssb:']
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const { activeTab, metamask, appState } = state
|
||||
const { metamask, appState } = state
|
||||
const {
|
||||
approvedOrigins,
|
||||
dismissedOrigins,
|
||||
lostAccounts,
|
||||
suggestedTokens,
|
||||
providerRequests,
|
||||
migratedPrivacyMode,
|
||||
featureFlags: {
|
||||
privacyMode,
|
||||
} = {},
|
||||
seedPhraseBackedUp,
|
||||
tokens,
|
||||
} = metamask
|
||||
const accountBalance = getCurrentEthBalance(state)
|
||||
const { forgottenPassword } = appState
|
||||
|
||||
const isUnconnected = Boolean(
|
||||
activeTab &&
|
||||
activeTabDappProtocols.includes(activeTab.protocol) &&
|
||||
privacyMode &&
|
||||
!approvedOrigins[activeTab.origin] &&
|
||||
!dismissedOrigins[activeTab.origin]
|
||||
)
|
||||
const isPopup = getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP
|
||||
|
||||
return {
|
||||
lostAccounts,
|
||||
forgottenPassword,
|
||||
suggestedTokens,
|
||||
unconfirmedTransactionsCount: unconfirmedTransactionsCountSelector(state),
|
||||
providerRequests,
|
||||
showPrivacyModeNotification: migratedPrivacyMode,
|
||||
activeTab,
|
||||
viewingUnconnectedDapp: isUnconnected && isPopup,
|
||||
shouldShowSeedPhraseReminder: !seedPhraseBackedUp && (parseInt(accountBalance, 16) > 0 || tokens.length > 0),
|
||||
isPopup,
|
||||
}
|
||||
@ -57,8 +37,6 @@ const mapStateToProps = state => {
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
unsetMigratedPrivacyMode: () => dispatch(unsetMigratedPrivacyMode()),
|
||||
forceApproveProviderRequestByOrigin: (origin) => dispatch(forceApproveProviderRequestByOrigin(origin)),
|
||||
rejectProviderRequestByOrigin: origin => dispatch(rejectProviderRequestByOrigin(origin)),
|
||||
})
|
||||
|
||||
export default compose(
|
||||
|
@ -30,10 +30,10 @@ export default class EnsInput extends Component {
|
||||
updateEnsResolution: PropTypes.func,
|
||||
scanQrCode: PropTypes.func,
|
||||
updateEnsResolutionError: PropTypes.func,
|
||||
addressBook: PropTypes.array,
|
||||
onPaste: PropTypes.func,
|
||||
onReset: PropTypes.func,
|
||||
onValidAddressTyped: PropTypes.func,
|
||||
contact: PropTypes.object,
|
||||
}
|
||||
|
||||
state = {
|
||||
@ -181,8 +181,7 @@ export default class EnsInput extends Component {
|
||||
|
||||
renderSelected () {
|
||||
const { t } = this.context
|
||||
const { className, selectedAddress, selectedName, addressBook } = this.props
|
||||
const contact = addressBook.filter(item => item.address === selectedAddress)[0] || {}
|
||||
const { className, selectedAddress, selectedName, contact = {} } = this.props
|
||||
const name = contact.name || selectedName
|
||||
|
||||
|
||||
|
@ -5,16 +5,19 @@ import {
|
||||
getSendToNickname,
|
||||
} from '../../send.selectors'
|
||||
import {
|
||||
getAddressBook,
|
||||
getAddressBookEntry,
|
||||
} from '../../../../selectors/selectors'
|
||||
const connect = require('react-redux').connect
|
||||
|
||||
|
||||
export default connect(
|
||||
state => ({
|
||||
network: getCurrentNetwork(state),
|
||||
selectedAddress: getSendTo(state),
|
||||
selectedName: getSendToNickname(state),
|
||||
addressBook: getAddressBook(state),
|
||||
})
|
||||
state => {
|
||||
const selectedAddress = getSendTo(state)
|
||||
return {
|
||||
network: getCurrentNetwork(state),
|
||||
selectedAddress,
|
||||
selectedName: getSendToNickname(state),
|
||||
contact: getAddressBookEntry(state, selectedAddress),
|
||||
}
|
||||
}
|
||||
)(EnsInput)
|
||||
|
@ -18,9 +18,10 @@ export default class SendContent extends Component {
|
||||
scanQrCode: PropTypes.func,
|
||||
showAddToAddressBookModal: PropTypes.func,
|
||||
showHexData: PropTypes.bool,
|
||||
to: PropTypes.string,
|
||||
ownedAccounts: PropTypes.array,
|
||||
addressBook: PropTypes.array,
|
||||
contact: PropTypes.object,
|
||||
isOwnedAccount: PropTypes.bool,
|
||||
}
|
||||
|
||||
updateGas = (updateData) => this.props.updateGas(updateData)
|
||||
@ -47,9 +48,7 @@ export default class SendContent extends Component {
|
||||
|
||||
maybeRenderAddContact () {
|
||||
const { t } = this.context
|
||||
const { to, addressBook = [], ownedAccounts = [], showAddToAddressBookModal } = this.props
|
||||
const isOwnedAccount = !!ownedAccounts.find(({ address }) => address.toLowerCase() === to.toLowerCase())
|
||||
const contact = addressBook.find(({ address }) => address === to) || {}
|
||||
const { isOwnedAccount, showAddToAddressBookModal, contact = {} } = this.props
|
||||
|
||||
if (isOwnedAccount || contact.name) {
|
||||
return
|
||||
|
@ -5,15 +5,17 @@ import {
|
||||
getSendTo,
|
||||
} from '../send.selectors'
|
||||
import {
|
||||
getAddressBook,
|
||||
getAddressBookEntry,
|
||||
} from '../../../selectors/selectors'
|
||||
import actions from '../../../store/actions'
|
||||
|
||||
function mapStateToProps (state) {
|
||||
const ownedAccounts = accountsWithSendEtherInfoSelector(state)
|
||||
const to = getSendTo(state)
|
||||
return {
|
||||
to: getSendTo(state),
|
||||
addressBook: getAddressBook(state),
|
||||
ownedAccounts: accountsWithSendEtherInfoSelector(state),
|
||||
isOwnedAccount: !!ownedAccounts.find(({ address }) => address.toLowerCase() === to.toLowerCase()),
|
||||
contact: getAddressBookEntry(state, to),
|
||||
to,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,11 +52,10 @@ describe('SendContent Component', function () {
|
||||
assert.equal(PageContainerContentChild.childAt(4).exists(), false)
|
||||
})
|
||||
|
||||
it('should not render the Dialog if addressBook contains "to" address', () => {
|
||||
it('should not render the Dialog if contact has a name', () => {
|
||||
wrapper.setProps({
|
||||
showHexData: false,
|
||||
to: '0x80F061544cC398520615B5d3e7A3BedD70cd4510',
|
||||
addressBook: [{ address: '0x80F061544cC398520615B5d3e7A3BedD70cd4510', name: 'dinodan' }],
|
||||
contact: { name: 'testName' },
|
||||
})
|
||||
const PageContainerContentChild = wrapper.find(PageContainerContent).children()
|
||||
assert(PageContainerContentChild.childAt(0).is(SendAssetRow), 'row[1] should be SendAssetRow')
|
||||
@ -65,12 +64,10 @@ describe('SendContent Component', function () {
|
||||
assert.equal(PageContainerContentChild.childAt(3).exists(), false)
|
||||
})
|
||||
|
||||
it('should not render the Dialog if ownedAccounts contains "to" address', () => {
|
||||
it('should not render the Dialog if it is an ownedAccount', () => {
|
||||
wrapper.setProps({
|
||||
showHexData: false,
|
||||
to: '0x80F061544cC398520615B5d3e7A3BedD70cd4510',
|
||||
addressBook: [],
|
||||
ownedAccounts: [{ address: '0x80F061544cC398520615B5d3e7A3BedD70cd4510', name: 'dinodan' }],
|
||||
isOwnedAccount: true,
|
||||
})
|
||||
const PageContainerContentChild = wrapper.find(PageContainerContent).children()
|
||||
assert(PageContainerContentChild.childAt(0).is(SendAssetRow), 'row[1] should be SendAssetRow')
|
||||
|
@ -38,6 +38,7 @@ proxyquire('../send-footer.container.js', {
|
||||
getSendEditingTransactionId: (s) => `mockEditingTransactionId:${s}`,
|
||||
getSendFromObject: (s) => `mockFromObject:${s}`,
|
||||
getSendTo: (s) => `mockTo:${s}`,
|
||||
getSendToNickname: (s) => `mockToNickname:${s}`,
|
||||
getSendToAccounts: (s) => `mockToAccounts:${s}`,
|
||||
getTokenBalance: (s) => `mockTokenBalance:${s}`,
|
||||
getSendHexData: (s) => `mockHexData:${s}`,
|
||||
|
@ -157,7 +157,6 @@ module.exports = {
|
||||
{ id: 'shapeShiftTx2', 'time': 1575000000000 },
|
||||
{ id: 'shapeShiftTx3', 'time': 1475000000000 },
|
||||
],
|
||||
'lostAccounts': [],
|
||||
'send': {
|
||||
'gasLimit': '0xFFFF',
|
||||
'gasPrice': '0xaa',
|
||||
|
@ -0,0 +1,31 @@
|
||||
import React, { PureComponent } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
export default class ConnectedSiteRow extends PureComponent {
|
||||
static defaultProps = {
|
||||
siteTitle: null,
|
||||
siteImage: null,
|
||||
onDelete: () => {},
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
siteTitle: PropTypes.string,
|
||||
siteImage: PropTypes.string,
|
||||
origin: PropTypes.string.isRequired,
|
||||
onDelete: PropTypes.func,
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
origin,
|
||||
onDelete,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<div className="connected-site-row">
|
||||
<div className="connected-site-row__origin">{origin}</div>
|
||||
<div className="connected-site-row__delete" onClick={onDelete}><i className="fa fa-trash" /></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
export { default } from './connected-site-row.component'
|
@ -0,0 +1,14 @@
|
||||
.connected-site-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
&__origin {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
&__delete {
|
||||
padding: 8px;
|
||||
}
|
||||
}
|