Merge pull request #7066 from MetaMask/Version-v7.1.0

Version 7.1.0
feature/default_network_editable
Dan Finlay 5 years ago committed by GitHub
commit 3f1229ce80
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 21
      .circleci/config.yml
  2. 13
      CHANGELOG.md
  3. 12
      app/_locales/cs/messages.json
  4. 12
      app/_locales/de/messages.json
  5. 57
      app/_locales/en/messages.json
  6. 12
      app/_locales/es/messages.json
  7. 12
      app/_locales/fr/messages.json
  8. 12
      app/_locales/hn/messages.json
  9. 12
      app/_locales/ht/messages.json
  10. 163
      app/_locales/it/messages.json
  11. 12
      app/_locales/ja/messages.json
  12. 12
      app/_locales/ko/messages.json
  13. 12
      app/_locales/nl/messages.json
  14. 12
      app/_locales/ph/messages.json
  15. 12
      app/_locales/pt/messages.json
  16. 12
      app/_locales/ru/messages.json
  17. 12
      app/_locales/sk/messages.json
  18. 12
      app/_locales/sl/messages.json
  19. 12
      app/_locales/th/messages.json
  20. 12
      app/_locales/tml/messages.json
  21. 12
      app/_locales/tr/messages.json
  22. 12
      app/_locales/vi/messages.json
  23. 12
      app/_locales/zh_CN/messages.json
  24. 12
      app/_locales/zh_TW/messages.json
  25. BIN
      app/images/coinswitch_logo.png
  26. BIN
      app/images/ethereum-metamask-chrome.png
  27. BIN
      app/images/icon-64.png
  28. BIN
      app/images/key-32.png
  29. BIN
      app/images/logo.png
  30. BIN
      app/images/pw128x128.png
  31. BIN
      app/images/shapeshift logo.png
  32. 2
      app/manifest.json
  33. 5
      app/scripts/background.js
  34. 8
      app/scripts/contentscript.js
  35. 284
      app/scripts/controllers/incoming-transactions.js
  36. 2
      app/scripts/controllers/network/createMetamaskMiddleware.js
  37. 2
      app/scripts/controllers/preferences.js
  38. 100
      app/scripts/controllers/provider-approval.js
  39. 11
      app/scripts/inpage.js
  40. 1
      app/scripts/lib/typed-message-manager.js
  41. 24
      app/scripts/lib/util.js
  42. 77
      app/scripts/metamask-controller.js
  43. 30
      app/scripts/migrations/036.js
  44. 3
      app/scripts/migrations/index.js
  45. 1
      development/states/account-list-with-imported.json
  46. 3
      development/states/accounts-loose.json
  47. 1
      development/states/add-token.json
  48. 3
      development/states/compilation-bug.json
  49. 1
      development/states/conf-tx.json
  50. 1
      development/states/confirm-new-ui.json
  51. 2
      development/states/confirm-sig-requests.json
  52. 2
      development/states/currency-localization.json
  53. 1
      development/states/first-time.json
  54. 3
      development/states/import-private-key-warning.json
  55. 3
      development/states/import-private-key.json
  56. 89
      development/states/lost-accounts.json
  57. 3
      development/states/navigate-txs.json
  58. 1
      development/states/pending-tx-insufficient.json
  59. 3
      development/states/pending-tx.json
  60. 3
      development/states/personal-sign.json
  61. 1
      development/states/private-key-export-success.json
  62. 1
      development/states/private-key-export.json
  63. 1
      development/states/send-edit.json
  64. 2
      development/states/send-new-ui.json
  65. 1
      development/states/send.json
  66. 2
      development/states/tx-list-items.json
  67. BIN
      docs/transaction-flow.png
  68. 241
      gentests.js
  69. 2
      package.json
  70. 1
      test/data/2-state.json
  71. 4
      test/data/mock-state.json
  72. 20
      test/e2e/metamask-ui.spec.js
  73. 658
      test/unit/app/controllers/incoming-transactions-test.js
  74. 8
      test/unit/app/controllers/metamask-controller-test.js
  75. 260
      test/unit/app/controllers/provider-approval-test.js
  76. 119
      test/unit/migrations/036-test.js
  77. 3
      ui/app/components/app/confirm-page-container/confirm-page-container.component.js
  78. 25
      ui/app/components/app/signature-request.js
  79. 3
      ui/app/components/app/transaction-breakdown/index.scss
  80. 5
      ui/app/components/app/transaction-list-item/transaction-list-item.component.js
  81. 12
      ui/app/components/app/transaction-list-item/transaction-list-item.container.js
  82. 5
      ui/app/components/ui/sender-to-recipient/sender-to-recipient.component.js
  83. 3
      ui/app/helpers/constants/routes.js
  84. 1
      ui/app/helpers/constants/transactions.js
  85. 5
      ui/app/helpers/utils/transactions.util.js
  86. 4
      ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js
  87. 4
      ui/app/pages/confirm-transaction-base/confirm-transaction-base.container.js
  88. 2
      ui/app/pages/first-time-flow/end-of-flow/end-of-flow.component.js
  89. 29
      ui/app/pages/home/home.component.js
  90. 24
      ui/app/pages/home/home.container.js
  91. 5
      ui/app/pages/send/send-content/add-recipient/ens-input.component.js
  92. 17
      ui/app/pages/send/send-content/add-recipient/ens-input.container.js
  93. 7
      ui/app/pages/send/send-content/send-content.component.js
  94. 10
      ui/app/pages/send/send-content/send-content.container.js
  95. 11
      ui/app/pages/send/send-content/tests/send-content-component.test.js
  96. 1
      ui/app/pages/send/send-footer/tests/send-footer-container.test.js
  97. 1
      ui/app/pages/send/tests/send-selectors-test-data.js
  98. 31
      ui/app/pages/settings/connections-tab/connected-site-row/connected-site-row.component.js
  99. 1
      ui/app/pages/settings/connections-tab/connected-site-row/index.js
  100. 14
      ui/app/pages/settings/connections-tab/connected-site-row/index.scss
  101. Some files were not shown because too many files have changed in this diff Show More

@ -74,6 +74,9 @@ workflows:
- prep-build - prep-build
# - prep-docs # - prep-docs
- all-tests-pass - all-tests-pass
- coveralls-upload:
requires:
- test-unit
jobs: jobs:
create_release_pull_request: create_release_pull_request:
@ -204,7 +207,7 @@ jobs:
at: . at: .
- run: - run:
name: test:e2e:firefox name: test:e2e:firefox
command: yarn build:test && yarn test:e2e:chrome command: yarn build:test && yarn test:e2e:firefox
no_output_timeout: 20m no_output_timeout: 20m
- store_artifacts: - store_artifacts:
path: test-artifacts path: test-artifacts
@ -257,6 +260,11 @@ jobs:
- run: - run:
name: test:coverage name: test:coverage
command: yarn test:coverage command: yarn test:coverage
- persist_to_workspace:
root: .
paths:
- .nyc_output
- coverage
test-mozilla-lint: test-mozilla-lint:
docker: docker:
- image: circleci/node:10.16-browsers - image: circleci/node:10.16-browsers
@ -303,6 +311,17 @@ jobs:
name: All Tests Passed name: All Tests Passed
command: echo 'weew - everything 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: create_github_release:
docker: docker:
- image: circleci/node:8.15.1-browsers - image: circleci/node:8.15.1-browsers

@ -2,6 +2,19 @@
## Current Develop Branch ## 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 ## 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 - [#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": { "exposeAccounts": {
"message": "Vystavte účty" "message": "Vystavte účty"
}, },
@ -20,12 +14,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "Schválené údaje webových stránek byly úspěšně zrušeny." "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": { "clearApprovalData": {
"message": "Jasné údaje o schválení" "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": { "exposeAccounts": {
"message": "Expose Konten" "message": "Expose Konten"
}, },
@ -20,12 +14,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "Genehmigte Website-Daten wurden erfolgreich gelöscht." "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": { "clearApprovalData": {
"message": "Genehmigungsdaten löschen" "message": "Genehmigungsdaten löschen"
}, },

@ -1,21 +1,9 @@
{ {
"shareAddress": { "showIncomingTransactions": {
"message": "Share Address" "message": "Show Incoming Transactions"
}, },
"shareAddressToConnect": { "showIncomingTransactionsDescription": {
"message": "Share your address to connect to $1?" "message": "Select this to use Etherscan to show incoming transactions in the transactions list"
},
"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."
}, },
"exposeAccounts": { "exposeAccounts": {
"message": "Expose Accounts" "message": "Expose Accounts"
@ -32,20 +20,35 @@
"confirmClear": { "confirmClear": {
"message": "Are you sure you want to clear approved websites?" "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": { "contractInteraction": {
"message": "Contract Interaction" "message": "Contract Interaction"
}, },
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "Approved website data cleared successfully." "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": { "clearApprovalData": {
"message": "Clear Privacy Data" "message": "Remove all sites"
}, },
"reject": { "reject": {
"message": "Reject" "message": "Reject"
@ -399,10 +402,10 @@
"connectToTrezor": { "connectToTrezor": {
"message": "Connect to Trezor" "message": "Connect to Trezor"
}, },
"contactList": { "contacts": {
"message": "Contact List" "message": "Contacts"
}, },
"contactListDescription": { "contactsSettingsDescription": {
"message": "Add, edit, remove, and manage your contacts" "message": "Add, edit, remove, and manage your contacts"
}, },
"continue": { "continue": {
@ -620,7 +623,7 @@
"message": "If you ever have questions or see something fishy, email support@metamask.io." "message": "If you ever have questions or see something fishy, email support@metamask.io."
}, },
"endOfFlowMessage8": { "endOfFlowMessage8": {
"message": "MetaMask cannot recover your seedphrase. Learn more." "message": "MetaMask cannot recover your seedphrase."
}, },
"endOfFlowMessage9": { "endOfFlowMessage9": {
"message": "Learn more." "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": { "exposeAccounts": {
"message": "Exponer cuentas" "message": "Exponer cuentas"
}, },
@ -20,12 +14,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "Los datos aprobados del sitio web se borraron con éxito." "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": { "clearApprovalData": {
"message": "Borrar datos de aprobación" "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": { "exposeAccounts": {
"message": "Exposer les comptes" "message": "Exposer les comptes"
}, },
@ -20,12 +14,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "Les données de site Web approuvées ont été supprimées." "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": { "clearApprovalData": {
"message": "Effacer les données d'approbation" "message": "Effacer les données d'approbation"
}, },

@ -1,10 +1,4 @@
{ {
"privacyMode": {
"message": "गपनयतड"
},
"privacyModeDescription": {
"message": "वबसइट आपकनकखनिए पहच क अनध करन।"
},
"exposeAccounts": { "exposeAccounts": {
"message": "ख परश कर" "message": "ख परश कर"
}, },
@ -20,12 +14,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "सत वबसइट ड सफलतवक म।" "message": "सत वबसइट ड सफलतवक म।"
}, },
"approvalData": {
"message": "सि"
},
"approvalDataDescription": {
"message": "अनित वबसइट ड करि सभइटिर स अनदन क अनध करन।"
},
"clearApprovalData": { "clearApprovalData": {
"message": "अनदन ड कर" "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": { "exposeAccounts": {
"message": "Ekspoze Kont" "message": "Ekspoze Kont"
}, },
@ -20,12 +14,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "Done sou sit wèb apwouve yo te klarifye avèk siksè." "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": { "clearApprovalData": {
"message": "Klè Done sou vi prive" "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": { "exposeAccounts": {
"message": "Esponi Accounts" "message": "Esponi Accounts"
}, },
@ -20,20 +14,35 @@
"confirmClear": { "confirmClear": {
"message": "Sei sicuro di voler cancellare i siti Web approvati?" "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": { "contractInteraction": {
"message": "Interazione Contratto" "message": "Interazione Contratto"
}, },
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "Dati del sito Web approvati cancellati correttamente." "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": { "clearApprovalData": {
"message": "Cancella i dati di approvazione" "message": "Rimuovi tutti i siti"
}, },
"reject": { "reject": {
"message": "Annulla" "message": "Annulla"
@ -80,12 +89,21 @@
"activityLog": { "activityLog": {
"message": "log attività" "message": "log attività"
}, },
"add": {
"message": "Aggiungi"
},
"address": { "address": {
"message": "Indirizzo" "message": "Indirizzo"
}, },
"addNetwork": { "addNetwork": {
"message": "Aggiungi Rete" "message": "Aggiungi Rete"
}, },
"addRecipient": {
"message": "Aggiungi Destinatario"
},
"addressBook": {
"message": "Rubrica"
},
"advanced": { "advanced": {
"message": "Avanzate" "message": "Avanzate"
}, },
@ -98,6 +116,18 @@
"addCustomToken": { "addCustomToken": {
"message": "Aggiungi un token personalizzato" "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": { "addToken": {
"message": "Aggiungi Token" "message": "Aggiungi Token"
}, },
@ -172,6 +202,18 @@
"back": { "back": {
"message": "Indietro" "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": { "balance": {
"message": "Bilancio:" "message": "Bilancio:"
}, },
@ -237,9 +279,15 @@
"bytes": { "bytes": {
"message": "Bytes" "message": "Bytes"
}, },
"off": {
"message": "Off"
},
"ok": { "ok": {
"message": "Ok" "message": "Ok"
}, },
"on": {
"message": "On"
},
"optionalBlockExplorerUrl": { "optionalBlockExplorerUrl": {
"message": "URL del Block Explorer (opzionale)" "message": "URL del Block Explorer (opzionale)"
}, },
@ -348,6 +396,12 @@
"connectToTrezor": { "connectToTrezor": {
"message": "Connettersi al Trezor" "message": "Connettersi al Trezor"
}, },
"contacts": {
"message": "Contatti"
},
"contactsSettingsDescription": {
"message": "Aggiungi, modifica, rimuovi e gestisci i tuoi contatti"
},
"continue": { "continue": {
"message": "Continua" "message": "Continua"
}, },
@ -454,6 +508,12 @@
"defaultNetwork": { "defaultNetwork": {
"message": "La rete predefinita per transazioni in Ether è la Rete Ethereum Principale." "message": "La rete predefinita per transazioni in Ether è la Rete Ethereum Principale."
}, },
"delete": {
"message": "Elimina"
},
"deleteAccount": {
"message": "Elimina Account"
},
"denExplainer": { "denExplainer": {
"message": "Il DEN è il tuo archivio crittato con password dentro MetaMask." "message": "Il DEN è il tuo archivio crittato con password dentro MetaMask."
}, },
@ -493,6 +553,9 @@
"directDepositEtherExplainer": { "directDepositEtherExplainer": {
"message": "Se possiedi già degli Ether, questa è la via più veloce per aggiungere Ether al tuo portafoglio con un deposito diretto." "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": { "done": {
"message": "Finito" "message": "Finito"
}, },
@ -520,6 +583,9 @@
"editAccountName": { "editAccountName": {
"message": "Modifica Nome Account" "message": "Modifica Nome Account"
}, },
"editContact": {
"message": "Modifica Contatto"
},
"editingTransaction": { "editingTransaction": {
"message": "Modifica la transazione" "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." "message": "Se hai delle domande o vedi delle attività sospette, manda una mail a support@metamask.io."
}, },
"endOfFlowMessage8": { "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": { "ensNameNotFound": {
"message": "Nome ENS non trovato" "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": { "enterPassword": {
"message": "Inserisci password" "message": "Inserisci password"
}, },
@ -568,6 +649,9 @@
"eth": { "eth": {
"message": "ETH" "message": "ETH"
}, },
"ethereumPublicAddress": {
"message": "Indirizzo Pubblico Ethereum"
},
"etherscanView": { "etherscanView": {
"message": "Vedi account su Etherscan" "message": "Vedi account su Etherscan"
}, },
@ -656,7 +740,7 @@
"message": "Generando la frase seed..." "message": "Generando la frase seed..."
}, },
"gasPrice": { "gasPrice": {
"message": "Prezzo del Gas (GWEI)" "message": "Prezzo Gas (GWEI)"
}, },
"gasPriceExtremelyLow": { "gasPriceExtremelyLow": {
"message": "Prezzo del gas estremamente basso" "message": "Prezzo del gas estremamente basso"
@ -878,6 +962,9 @@
"loadingTokens": { "loadingTokens": {
"message": "Caricamento Tokens..." "message": "Caricamento Tokens..."
}, },
"loadMore": {
"message": "Carica altro"
},
"localhost": { "localhost": {
"message": "Localhost 8545" "message": "Localhost 8545"
}, },
@ -899,6 +986,9 @@
"memorizePhrase": { "memorizePhrase": {
"message": "Memorizza questa frase." "message": "Memorizza questa frase."
}, },
"memo": {
"message": "Promemoria"
},
"menu": { "menu": {
"message": "Menu" "message": "Menu"
}, },
@ -930,7 +1020,13 @@
"message": "Per favore inserisci la password per confermare che sei te!" "message": "Per favore inserisci la password per confermare che sei te!"
}, },
"myAccounts": { "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": { "mustSelectOne": {
"message": "Devi selezionare almeno un token." "message": "Devi selezionare almeno un token."
@ -964,10 +1060,16 @@
"newAccount": { "newAccount": {
"message": "Nuovo Account" "message": "Nuovo Account"
}, },
"newAccountDetectedDialogMessage": {
"message": "Rilevato nuovo indirizzo! Clica qua per aggiungerlo alla rubrica."
},
"newAccountNumberName": { "newAccountNumberName": {
"message": "Account $1", "message": "Account $1",
"description": "Nome predefinito per il prossimo account da creare nella schermata di creazione account" "description": "Nome predefinito per il prossimo account da creare nella schermata di creazione account"
}, },
"newContact": {
"message": "Nuovo Contatto"
},
"newContract": { "newContract": {
"message": "Nuovo Contratto" "message": "Nuovo Contratto"
}, },
@ -1178,9 +1280,15 @@
"receive": { "receive": {
"message": "Ricevi" "message": "Ricevi"
}, },
"recents": {
"message": "Recenti"
},
"recipientAddress": { "recipientAddress": {
"message": "Indirizzo Destinatario" "message": "Indirizzo Destinatario"
}, },
"recipientAddressPlaceholder": {
"message": "Ricerca, indirizzo pubblico (0x), o ENS"
},
"refundAddress": { "refundAddress": {
"message": "Indirizzo di Rimborso" "message": "Indirizzo di Rimborso"
}, },
@ -1205,6 +1313,15 @@
"resetAccountDescription": { "resetAccountDescription": {
"message": "Ripristinare il tuo account cancellerà lo storico delle transazioni." "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": { "restoreFromSeed": {
"message": "Ripristina da una frase seed" "message": "Ripristina da una frase seed"
}, },
@ -1454,7 +1571,7 @@
"message": "ci può essere solo uno spazio tra le parole" "message": "ci può essere solo uno spazio tra le parole"
}, },
"speedUp": { "speedUp": {
"message": "velocizza" "message": "Velocizza"
}, },
"speedUpTitle": { "speedUpTitle": {
"message": "Velocizza Transazione" "message": "Velocizza Transazione"
@ -1649,6 +1766,9 @@
"transfer": { "transfer": {
"message": "Trasferisci" "message": "Trasferisci"
}, },
"transferBetweenAccounts": {
"message": "Trasferisci tra i miei accounts"
},
"transferFrom": { "transferFrom": {
"message": "Trasferisci Da" "message": "Trasferisci Da"
}, },
@ -1729,6 +1849,9 @@
"useOldUI": { "useOldUI": {
"message": "Use la vecchia UI" "message": "Use la vecchia UI"
}, },
"userName": {
"message": "Username"
},
"validFileImport": { "validFileImport": {
"message": "Devi selezionare un file valido da importare." "message": "Devi selezionare un file valido da importare."
}, },
@ -1738,6 +1861,12 @@
"viewAccount": { "viewAccount": {
"message": "Vedi Account" "message": "Vedi Account"
}, },
"viewinExplorer": {
"message": "Vedi in Explorer"
},
"viewContact": {
"message": "Visualizza Contatto"
},
"viewOnCustomBlockExplorer": { "viewOnCustomBlockExplorer": {
"message": "Vedi su $1" "message": "Vedi su $1"
}, },

@ -1,10 +1,4 @@
{ {
"privacyMode": {
"message": "プライバシーモード"
},
"privacyModeDescription": {
"message": "ウェブサイトはあなたのアカウント情報を閲覧するためのアクセスを要求する必要があります。"
},
"exposeAccounts": { "exposeAccounts": {
"message": "アカウントを公開する" "message": "アカウントを公開する"
}, },
@ -20,12 +14,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "承認されたウェブサイトデータが正常に消去されました。" "message": "承認されたウェブサイトデータが正常に消去されました。"
}, },
"approvalData": {
"message": "承認データ"
},
"approvalDataDescription": {
"message": "承認されたウェブサイトのデータをクリアすると、すべてのサイトで承認を再度要求する必要があります"
},
"clearApprovalData": { "clearApprovalData": {
"message": "承認データのクリア" "message": "承認データのクリア"
}, },

@ -1,10 +1,4 @@
{ {
"privacyMode": {
"message": "개인 정보 보호 모드"
},
"privacyModeDescription": {
"message": "웹 사이트는 계정 정보를 볼 수있는 액세스 권한을 요청해야합니다."
},
"exposeAccounts": { "exposeAccounts": {
"message": "계정 노출" "message": "계정 노출"
}, },
@ -20,12 +14,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "승인 된 웹 사이트 데이터가 성공적으로 삭제되었습니다." "message": "승인 된 웹 사이트 데이터가 성공적으로 삭제되었습니다."
}, },
"approvalData": {
"message": "승인 데이터"
},
"approvalDataDescription": {
"message": "승인 된 웹 사이트 데이터를 삭제하여 모든 사이트에서 승인을 다시 요청해야합니다."
},
"clearApprovalData": { "clearApprovalData": {
"message": "승인 데이터 삭제" "message": "승인 데이터 삭제"
}, },

@ -1,10 +1,4 @@
{ {
"privacyMode": {
"message": "Privacy-modus"
},
"privacyModeDescription": {
"message": "Websites moeten toegang vragen om uw accountgegevens te bekijken."
},
"exposeAccounts": { "exposeAccounts": {
"message": "Expose Accounts" "message": "Expose Accounts"
}, },
@ -20,12 +14,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "Goedgekeurde websitegegevens zijn met succes gewist." "message": "Goedgekeurde websitegegevens zijn met succes gewist."
}, },
"approvalData": {
"message": "Goedkeuringsgegevens"
},
"approvalDataDescription": {
"message": "Goedgekeurde websitegegevens wissen zodat alle sites opnieuw goedkeuring moeten aanvragen."
},
"clearApprovalData": { "clearApprovalData": {
"message": "Gegevens over goedkeuring wissen" "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": { "exposeAccounts": {
"message": "Ilantad ang Mga Account" "message": "Ilantad ang Mga Account"
}, },
@ -20,12 +14,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "Matagumpay na na-clear ang data ng aprubadong website." "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": { "clearApprovalData": {
"message": "Tanggalin ang data ng pag-apruba" "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": { "exposeAccounts": {
"message": "Expor contas" "message": "Expor contas"
}, },
@ -20,12 +14,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "Dados aprovados do website foram limpos com sucesso." "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": { "clearApprovalData": {
"message": "Limpar dados de aprovação" "message": "Limpar dados de aprovação"
}, },

@ -1,10 +1,4 @@
{ {
"privacyMode": {
"message": "Режим конфиденциальности"
},
"privacyModeDescription": {
"message": "Веб-сайты должны запрашивать доступ для просмотра информации об учетной записи."
},
"exposeAccounts": { "exposeAccounts": {
"message": "Открыть счета" "message": "Открыть счета"
}, },
@ -20,12 +14,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "Утвержденные данные веб-сайта успешно удалены." "message": "Утвержденные данные веб-сайта успешно удалены."
}, },
"approvalData": {
"message": "Данные об утверждении"
},
"approvalDataDescription": {
"message": "Очистите утвержденные данные веб-сайта, чтобы все сайты снова запросили подтверждение."
},
"clearApprovalData": { "clearApprovalData": {
"message": "Четкие данные об утверждении" "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": { "exposeAccounts": {
"message": "Vystavte účty" "message": "Vystavte účty"
}, },
@ -20,12 +14,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "Schválené údaje webových stránek byly úspěšně zrušeny." "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": { "clearApprovalData": {
"message": "Jasné údaje o schválení" "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": { "privacyNotice": {
"message": "Obvestilo o zasebnosti" "message": "Obvestilo o zasebnosti"
}, },
@ -26,12 +20,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "Odobrene spletne strani uspešno počiščene." "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": { "clearApprovalData": {
"message": "Počisti podatke o odobritvi" "message": "Počisti podatke o odobritvi"
}, },

@ -1,10 +1,4 @@
{ {
"privacyMode": {
"message": "โหมดความเปนสวนตว"
},
"privacyModeDescription": {
"message": "เวบไซตองขอเขาถงเพอดอมลบญชของคณ"
},
"exposeAccounts": { "exposeAccounts": {
"message": "เปดเผยบญช" "message": "เปดเผยบญช"
}, },
@ -20,12 +14,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "อนอมลเวบไซตไดบอนแลว" "message": "อนอมลเวบไซตไดบอนแลว"
}, },
"approvalData": {
"message": "ขอมลการอน"
},
"approvalDataDescription": {
"message": "ลางขอมลเวบไซตไดบการอนเพอใหกไซตองขออนกครง"
},
"clearApprovalData": { "clearApprovalData": {
"message": "ลางขอมลการอน" "message": "ลางขอมลการอน"
}, },

@ -1,10 +1,4 @@
{ {
"privacyMode": {
"message": "தனிி"
},
"privacyModeDescription": {
"message": "உஙகள கணக தகவலிட வலதளஙகள அணகலர வ."
},
"exposeAccounts": { "exposeAccounts": {
"message": "கணககள அமபலபபடகள" "message": "கணககள அமபலபபடகள"
}, },
@ -20,12 +14,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "அஙகரிகபபடட வலதள தரவிகரமக அழிகபபடடத." "message": "அஙகரிகபபடட வலதள தரவிகரமக அழிகபபடடத."
}, },
"approvalData": {
"message": "ஒபதல தரவ"
},
"approvalDataDescription": {
"message": "அஙகரிகபபடட வலதள தரவ அழிகவ, அன தளஙகள ஒபதலர வ."
},
"clearApprovalData": { "clearApprovalData": {
"message": "ஒபதல தரவ அழி" "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": { "exposeAccounts": {
"message": "Hesapları Açığa Çıkar" "message": "Hesapları Açığa Çıkar"
}, },
@ -20,12 +14,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "Onaylanan web sitesi verileri başarıyla temizlendi." "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": { "clearApprovalData": {
"message": "Onay verilerini temizle" "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": { "exposeAccounts": {
"message": "Hiển thị tài khoản" "message": "Hiển thị tài khoản"
}, },
@ -20,12 +14,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "Đã xóa thành công dữ liệu trang web được phê duyệt." "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": { "clearApprovalData": {
"message": "Xóa dữ liệu phê duyệt" "message": "Xóa dữ liệu phê duyệt"
}, },

@ -1,10 +1,4 @@
{ {
"privacyMode": {
"message": "隐私模式"
},
"privacyModeDescription": {
"message": "网站必须请求访问权限才能查看您的帐户信息。"
},
"exposeAccounts": { "exposeAccounts": {
"message": "公开账户" "message": "公开账户"
}, },
@ -20,12 +14,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "已批准的网站数据已成功清除。" "message": "已批准的网站数据已成功清除。"
}, },
"approvalData": {
"message": "审批数据"
},
"approvalDataDescription": {
"message": "清除已批准的网站数据,以便所有网站都必须再次申请"
},
"clearApprovalData": { "clearApprovalData": {
"message": "清除批准数据" "message": "清除批准数据"
}, },

@ -1,10 +1,4 @@
{ {
"privacyMode": {
"message": "隱私模式"
},
"privacyModeDescription": {
"message": "網站必須請求訪問權限才能查看您的帳戶資訊"
},
"exposeAccounts": { "exposeAccounts": {
"message": "公開賬戶" "message": "公開賬戶"
}, },
@ -23,12 +17,6 @@
"clearApprovalDataSuccess": { "clearApprovalDataSuccess": {
"message": "已批准的網站紀錄已成功清除。" "message": "已批准的網站紀錄已成功清除。"
}, },
"approvalData": {
"message": "審核紀錄"
},
"approvalDataDescription": {
"message": "清除之前已批准過的網站審核紀錄,所有網站都必須再次申請"
},
"clearApprovalData": { "clearApprovalData": {
"message": "清除批准數據" "message": "清除批准數據"
}, },

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 692 B

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 17 KiB

@ -1,7 +1,7 @@
{ {
"name": "__MSG_appName__", "name": "__MSG_appName__",
"short_name": "__MSG_appName__", "short_name": "__MSG_appName__",
"version": "7.0.1", "version": "7.1.0",
"manifest_version": 2, "manifest_version": 2,
"author": "https://metamask.io", "author": "https://metamask.io",
"description": "__MSG_appDescription__", "description": "__MSG_appDescription__",

@ -142,7 +142,6 @@ setupMetamaskMeshMetrics()
* @property {Object} infuraNetworkStatus - An object of infura network status checks. * @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 {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} 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. * @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.messageManager.on('updateBadge', updateBadge)
controller.personalMessageManager.on('updateBadge', updateBadge) controller.personalMessageManager.on('updateBadge', updateBadge)
controller.typedMessageManager.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. * 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 unapprovedMsgCount = controller.messageManager.unapprovedMsgCount
const unapprovedPersonalMsgs = controller.personalMessageManager.unapprovedPersonalMsgCount const unapprovedPersonalMsgs = controller.personalMessageManager.unapprovedPersonalMsgCount
const unapprovedTypedMsgs = controller.typedMessageManager.unapprovedTypedMessagesCount 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 const count = unapprovedTxCount + unapprovedMsgCount + unapprovedPersonalMsgs + unapprovedTypedMsgs + pendingProviderRequests
if (count) { if (count) {
label = String(count) label = String(count)

@ -114,7 +114,6 @@ function forwardTrafficBetweenMuxers (channelName, muxA, muxB) {
async function setupPublicApi (outStream) { async function setupPublicApi (outStream) {
const api = { const api = {
forceReloadSite: (cb) => cb(null, forceReloadSite()),
getSiteMetadata: (cb) => cb(null, getSiteMetadata()), getSiteMetadata: (cb) => cb(null, getSiteMetadata()),
} }
const dnode = Dnode(api) const dnode = Dnode(api)
@ -307,10 +306,3 @@ async function domIsReady () {
// wait for load // wait for load
await new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve, { once: true })) await new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve, { once: true }))
} }
/**
* Reloads the site
*/
function forceReloadSite () {
window.location.reload()
}

@ -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, processEthSignMessage,
processTypedMessage, processTypedMessage,
processTypedMessageV3, processTypedMessageV3,
processTypedMessageV4,
processPersonalMessage, processPersonalMessage,
getPendingNonce, getPendingNonce,
}) { }) {
@ -27,6 +28,7 @@ function createMetamaskMiddleware ({
processEthSignMessage, processEthSignMessage,
processTypedMessage, processTypedMessage,
processTypedMessageV3, processTypedMessageV3,
processTypedMessageV4,
processPersonalMessage, processPersonalMessage,
}), }),
createPendingNonceMiddleware({ getPendingNonce }), createPendingNonceMiddleware({ getPendingNonce }),

@ -41,7 +41,7 @@ class PreferencesController {
// for convenient testing of pre-release features, and should never // for convenient testing of pre-release features, and should never
// perform sensitive operations. // perform sensitive operations.
featureFlags: { featureFlags: {
privacyMode: true, showIncomingTransactions: true,
}, },
knownMethodData: {}, knownMethodData: {},
participateInMetaMetrics: null, 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 * A controller that services user-approved requests for a full Ethereum provider API
*/ */
class ProviderApprovalController extends SafeEventEmitter { class ProviderApprovalController extends SafeEventEmitter {
/**
* Determines if caching is enabled
*/
caching = true
/** /**
* Creates a ProviderApprovalController * Creates a ProviderApprovalController
* *
* @param {Object} [config] - Options to configure controller * @param {Object} [config] - Options to configure controller
*/ */
constructor ({ closePopup, keyringController, openPopup, preferencesController } = {}) { constructor ({ closePopup, initState, keyringController, openPopup, preferencesController } = {}) {
super() super()
this.closePopup = closePopup this.closePopup = closePopup
this.keyringController = keyringController this.keyringController = keyringController
this.openPopup = openPopup this.openPopup = openPopup
this.preferencesController = preferencesController this.preferencesController = preferencesController
this.store = new ObservableStore({ this.memStore = new ObservableStore({
approvedOrigins: {},
dismissedOrigins: {},
providerRequests: [], 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 * @param {string} siteImage - The icon of the window requesting full provider access
*/ */
_handleProviderRequest (origin, siteTitle, siteImage) { _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 isUnlocked = this.keyringController.memStore.getState().isUnlocked
const { approvedOrigins, dismissedOrigins } = this.store.getState() const { approvedOrigins } = this.store.getState()
const originAlreadyHandled = approvedOrigins[origin] || dismissedOrigins[origin] const originAlreadyHandled = approvedOrigins[origin]
if (originAlreadyHandled && this.caching && isUnlocked) { if (originAlreadyHandled && isUnlocked) {
return return
} }
this.openPopup && this.openPopup() this.openPopup && this.openPopup()
@ -85,23 +87,20 @@ class ProviderApprovalController extends SafeEventEmitter {
this.closePopup() this.closePopup()
} }
const { approvedOrigins, dismissedOrigins, providerRequests } = this.store.getState() const { approvedOrigins } = this.store.getState()
const { providerRequests } = this.memStore.getState()
let _dismissedOrigins = dismissedOrigins const providerRequest = providerRequests.find((request) => request.origin === origin)
if (dismissedOrigins[origin]) {
_dismissedOrigins = Object.assign({}, dismissedOrigins)
delete _dismissedOrigins[origin]
}
const remainingProviderRequests = providerRequests.filter(request => request.origin !== origin) const remainingProviderRequests = providerRequests.filter(request => request.origin !== origin)
this.store.updateState({ this.store.updateState({
approvedOrigins: { approvedOrigins: {
...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 }) this.emit(`resolvedRequest:${origin}`, { approved: true })
} }
@ -115,53 +114,21 @@ class ProviderApprovalController extends SafeEventEmitter {
this.closePopup() 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) const remainingProviderRequests = providerRequests.filter(request => request.origin !== origin)
// We're cloning and deleting keys here because we don't want to keep unneeded keys // We're cloning and deleting keys here because we don't want to keep unneeded keys
const _approvedOrigins = Object.assign({}, approvedOrigins) const _approvedOrigins = Object.assign({}, approvedOrigins)
delete _approvedOrigins[origin] delete _approvedOrigins[origin]
this.store.putState({ this.store.putState({ approvedOrigins: _approvedOrigins })
approvedOrigins: _approvedOrigins, this.memStore.putState({ providerRequests: remainingProviderRequests })
providerRequests: remainingProviderRequests,
dismissedOrigins: {
...dismissedOrigins,
[origin]: true,
},
})
this.emit(`resolvedRequest:${origin}`, { approved: false }) this.emit(`resolvedRequest:${origin}`, { approved: false })
} }
/** /**
* Silently approves access to a full Ethereum provider API for the origin * Clears any approvals for user-approved origins
*
* @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
*/ */
clearApprovedOrigins () { clearApprovedOrigins () {
this.store.updateState({ this.store.updateState({
@ -176,10 +143,17 @@ class ProviderApprovalController extends SafeEventEmitter {
* @returns {boolean} - True if the origin has been approved * @returns {boolean} - True if the origin has been approved
*/ */
shouldExposeAccounts (origin) { shouldExposeAccounts (origin) {
const privacyMode = this.preferencesController.getFeatureFlags().privacyMode return Boolean(this.store.getState().approvedOrigins[origin])
return !privacyMode || 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 module.exports = ProviderApprovalController

@ -61,8 +61,19 @@ const inpageProvider = new MetamaskInpageProvider(metamaskStream)
// set a high max listener count to avoid unnecesary warnings // set a high max listener count to avoid unnecesary warnings
inpageProvider.setMaxListeners(100) inpageProvider.setMaxListeners(100)
let warnedOfAutoRefreshDeprecation = false
// augment the provider with its enable method // augment the provider with its enable method
inpageProvider.enable = function ({ force } = {}) { 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) => { return new Promise((resolve, reject) => {
inpageProvider.sendAsync({ method: 'eth_requestAccounts', params: [force] }, (error, response) => { inpageProvider.sendAsync({ method: 'eth_requestAccounts', params: [force] }, (error, response) => {
if (error || response.error) { if (error || response.error) {

@ -141,6 +141,7 @@ module.exports = class TypedMessageManager extends EventEmitter {
}, 'Expected EIP712 typed data') }, 'Expected EIP712 typed data')
break break
case 'V3': case 'V3':
case 'V4':
let data let data
assert.equal(typeof params, 'object', 'Params should be an object.') assert.equal(typeof params, 'object', 'Params should be an object.')
assert.ok('data' in params, 'Params must include a data field.') 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 = { module.exports = {
removeListeners, removeListeners,
applyListeners, applyListeners,
@ -154,4 +177,5 @@ module.exports = {
hexToBn, hexToBn,
bnToHex, bnToHex,
BnMultiplyByFraction, BnMultiplyByFraction,
fetchWithTimeout,
} }

@ -30,6 +30,7 @@ const InfuraController = require('./controllers/infura')
const CachedBalancesController = require('./controllers/cached-balances') const CachedBalancesController = require('./controllers/cached-balances')
const OnboardingController = require('./controllers/onboarding') const OnboardingController = require('./controllers/onboarding')
const RecentBlocksController = require('./controllers/recent-blocks') const RecentBlocksController = require('./controllers/recent-blocks')
const IncomingTransactionsController = require('./controllers/incoming-transactions')
const MessageManager = require('./lib/message-manager') const MessageManager = require('./lib/message-manager')
const PersonalMessageManager = require('./lib/personal-message-manager') const PersonalMessageManager = require('./lib/personal-message-manager')
const TypedMessageManager = require('./lib/typed-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 EthQuery = require('eth-query')
const ethUtil = require('ethereumjs-util') const ethUtil = require('ethereumjs-util')
const sigUtil = require('eth-sig-util') const sigUtil = require('eth-sig-util')
const contractMap = require('eth-contract-metadata')
const { const {
AddressBookController, AddressBookController,
CurrencyRateController, CurrencyRateController,
@ -62,7 +64,6 @@ const {
} = require('gaba') } = require('gaba')
const backEndMetaMetricsEvent = require('./lib/backend-metametrics') const backEndMetaMetricsEvent = require('./lib/backend-metametrics')
module.exports = class MetamaskController extends EventEmitter { module.exports = class MetamaskController extends EventEmitter {
/** /**
@ -137,6 +138,13 @@ module.exports = class MetamaskController extends EventEmitter {
networkController: this.networkController, 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. // account tracker watches balances, nonces, and any code at their address.
this.accountTracker = new AccountTracker({ this.accountTracker = new AccountTracker({
provider: this.provider, provider: this.provider,
@ -148,8 +156,10 @@ module.exports = class MetamaskController extends EventEmitter {
this.on('controllerConnectionChanged', (activeControllerConnections) => { this.on('controllerConnectionChanged', (activeControllerConnections) => {
if (activeControllerConnections > 0) { if (activeControllerConnections > 0) {
this.accountTracker.start() this.accountTracker.start()
this.incomingTransactionsController.start()
} else { } else {
this.accountTracker.stop() this.accountTracker.stop()
this.incomingTransactionsController.stop()
} }
}) })
@ -251,6 +261,7 @@ module.exports = class MetamaskController extends EventEmitter {
this.providerApprovalController = new ProviderApprovalController({ this.providerApprovalController = new ProviderApprovalController({
closePopup: opts.closePopup, closePopup: opts.closePopup,
initState: initState.ProviderApprovalController,
keyringController: this.keyringController, keyringController: this.keyringController,
openPopup: opts.openPopup, openPopup: opts.openPopup,
preferencesController: this.preferencesController, preferencesController: this.preferencesController,
@ -268,6 +279,8 @@ module.exports = class MetamaskController extends EventEmitter {
InfuraController: this.infuraController.store, InfuraController: this.infuraController.store,
CachedBalancesController: this.cachedBalancesController.store, CachedBalancesController: this.cachedBalancesController.store,
OnboardingController: this.onboardingController.store, OnboardingController: this.onboardingController.store,
ProviderApprovalController: this.providerApprovalController.store,
IncomingTransactionsController: this.incomingTransactionsController.store,
}) })
this.memStore = new ComposableObservableStore(null, { this.memStore = new ComposableObservableStore(null, {
@ -288,8 +301,11 @@ module.exports = class MetamaskController extends EventEmitter {
CurrencyController: this.currencyRateController, CurrencyController: this.currencyRateController,
ShapeshiftController: this.shapeshiftController, ShapeshiftController: this.shapeshiftController,
InfuraController: this.infuraController.store, InfuraController: this.infuraController.store,
ProviderApprovalController: this.providerApprovalController.store,
OnboardingController: this.onboardingController.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)) this.memStore.subscribe(this.sendUpdate.bind(this))
} }
@ -325,6 +341,7 @@ module.exports = class MetamaskController extends EventEmitter {
processEthSignMessage: this.newUnsignedMessage.bind(this), processEthSignMessage: this.newUnsignedMessage.bind(this),
processTypedMessage: this.newUnsignedTypedMessage.bind(this), processTypedMessage: this.newUnsignedTypedMessage.bind(this),
processTypedMessageV3: this.newUnsignedTypedMessage.bind(this), processTypedMessageV3: this.newUnsignedTypedMessage.bind(this),
processTypedMessageV4: this.newUnsignedTypedMessage.bind(this),
processPersonalMessage: this.newUnsignedPersonalMessage.bind(this), processPersonalMessage: this.newUnsignedPersonalMessage.bind(this),
getPendingNonce: this.getPendingNonce.bind(this), getPendingNonce: this.getPendingNonce.bind(this),
} }
@ -385,10 +402,6 @@ module.exports = class MetamaskController extends EventEmitter {
return { return {
...{ isInitialized }, ...{ isInitialized },
...this.memStore.getFlatState(), ...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), whitelistPhishingDomain: this.whitelistPhishingDomain.bind(this),
// AddressController // AddressController
setAddressBook: this.addressBookController.set.bind(this.addressBookController), setAddressBook: nodeify(this.addressBookController.set, this.addressBookController),
removeFromAddressBook: this.addressBookController.delete.bind(this.addressBookController), removeFromAddressBook: this.addressBookController.delete.bind(this.addressBookController),
// AppStateController // AppStateController
@ -507,7 +520,6 @@ module.exports = class MetamaskController extends EventEmitter {
// provider approval // provider approval
approveProviderRequestByOrigin: providerApprovalController.approveProviderRequestByOrigin.bind(providerApprovalController), approveProviderRequestByOrigin: providerApprovalController.approveProviderRequestByOrigin.bind(providerApprovalController),
rejectProviderRequestByOrigin: providerApprovalController.rejectProviderRequestByOrigin.bind(providerApprovalController), rejectProviderRequestByOrigin: providerApprovalController.rejectProviderRequestByOrigin.bind(providerApprovalController),
forceApproveProviderRequestByOrigin: providerApprovalController.forceApproveProviderRequestByOrigin.bind(providerApprovalController),
clearApprovedOrigins: providerApprovalController.clearApprovedOrigins.bind(providerApprovalController), clearApprovedOrigins: providerApprovalController.clearApprovedOrigins.bind(providerApprovalController),
// onboarding controller // onboarding controller
@ -639,8 +651,24 @@ module.exports = class MetamaskController extends EventEmitter {
tokens, tokens,
} = this.preferencesController.store.getState() } = 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 = { const preferences = {
accountTokens, accountTokens: filteredAccountTokens,
currentLocale, currentLocale,
frequentRpcList, frequentRpcList,
identities, identities,
@ -1114,6 +1142,9 @@ module.exports = class MetamaskController extends EventEmitter {
case 'V3': case 'V3':
signature = sigUtil.signTypedData(privKey, { data: JSON.parse(cleanMsgParams.data) }) signature = sigUtil.signTypedData(privKey, { data: JSON.parse(cleanMsgParams.data) })
break break
case 'V4':
signature = sigUtil.signTypedData_v4(privKey, { data: JSON.parse(cleanMsgParams.data) })
break
} }
} else { } else {
signature = await keyring.signTypedData(address, cleanMsgParams.data) 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. * @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) // END (VAULT / KEYRING RELATED METHODS)
//============================================================================= //=============================================================================
@ -1298,8 +1308,6 @@ module.exports = class MetamaskController extends EventEmitter {
const publicApi = this.setupPublicApi(mux.createStream('publicApi'), originDomain) const publicApi = this.setupPublicApi(mux.createStream('publicApi'), originDomain)
this.setupProviderConnection(mux.createStream('provider'), originDomain, publicApi) this.setupProviderConnection(mux.createStream('provider'), originDomain, publicApi)
this.setupPublicConfig(mux.createStream('publicConfig'), originDomain) 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 = { const publicApi = {
// wrap with an await remote // wrap with an await remote
forceReloadSite: async () => {
const remote = await getRemote()
return await pify(remote.forceReloadSite)()
},
getSiteMetadata: async () => { getSiteMetadata: async () => {
const remote = await getRemote() const remote = await getRemote()
return await pify(remote.getSiteMetadata)() return await pify(remote.getSiteMetadata)()
@ -1797,4 +1801,3 @@ module.exports = class MetamaskController extends EventEmitter {
return this.keyringController.setLocked() return this.keyringController.setLocked()
} }
} }

@ -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('./031'),
require('./032'), require('./032'),
require('./033'), require('./033'),
require('./034'),
require('./035'),
require('./036'),
] ]

@ -60,7 +60,6 @@
] ]
} }
], ],
"lostAccounts": [],
"seedWords": null "seedWords": null
}, },
"appState": { "appState": {

@ -102,8 +102,7 @@
"aa25854c0379e53c957ac9382e720c577fa31fd5" "aa25854c0379e53c957ac9382e720c577fa31fd5"
] ]
} }
], ]
"lostAccounts": []
}, },
"appState": { "appState": {
"menuOpen": false, "menuOpen": false,

@ -93,7 +93,6 @@
"type": "testnet" "type": "testnet"
}, },
"shapeShiftTxList": [], "shapeShiftTxList": [],
"lostAccounts": [],
"send": { "send": {
"gasLimit": null, "gasLimit": null,
"gasPrice": null, "gasPrice": null,

@ -103,8 +103,7 @@
"keyringTypes": [ "keyringTypes": [
"Simple Key Pair", "Simple Key Pair",
"HD Key Tree" "HD Key Tree"
], ]
"lostAccounts": []
}, },
"appState": { "appState": {
"menuOpen": false, "menuOpen": false,

@ -191,7 +191,6 @@
"type": "testnet" "type": "testnet"
}, },
"shapeShiftTxList": [], "shapeShiftTxList": [],
"lostAccounts": [],
"frequentRpcListDetail": [] "frequentRpcListDetail": []
}, },
"appState": { "appState": {

@ -110,7 +110,6 @@
"type": "testnet" "type": "testnet"
}, },
"shapeShiftTxList": [], "shapeShiftTxList": [],
"lostAccounts": [],
"send": { "send": {
"gasLimit": "0xea60", "gasLimit": "0xea60",
"gasPrice": "0xba43b7400", "gasPrice": "0xba43b7400",

@ -63,6 +63,7 @@
], ],
"tokens": [], "tokens": [],
"transactions": {}, "transactions": {},
"incomingTransactions": {},
"selectedAddressTxList": [], "selectedAddressTxList": [],
"unapprovedTxs": {}, "unapprovedTxs": {},
"unapprovedMsgs": { "unapprovedMsgs": {
@ -133,7 +134,6 @@
"type": "testnet" "type": "testnet"
}, },
"shapeShiftTxList": [], "shapeShiftTxList": [],
"lostAccounts": [],
"send": { "send": {
"gasLimit": "0xea60", "gasLimit": "0xea60",
"gasPrice": "0xba43b7400", "gasPrice": "0xba43b7400",

@ -64,6 +64,7 @@
], ],
"tokens": [], "tokens": [],
"transactions": {}, "transactions": {},
"incomingTransactions": {},
"selectedAddressTxList": [], "selectedAddressTxList": [],
"unapprovedMsgs": {}, "unapprovedMsgs": {},
"unapprovedMsgCount": 0, "unapprovedMsgCount": 0,
@ -95,7 +96,6 @@
"type": "testnet" "type": "testnet"
}, },
"shapeShiftTxList": [], "shapeShiftTxList": [],
"lostAccounts": [],
"send": { "send": {
"gasLimit": null, "gasLimit": null,
"gasPrice": null, "gasPrice": null,

@ -34,7 +34,6 @@
"type": "testnet" "type": "testnet"
}, },
"shapeShiftTxList": [], "shapeShiftTxList": [],
"lostAccounts": [],
"tokens": [], "tokens": [],
"currentLocale": "en", "currentLocale": "en",
"preferences": { "preferences": {

@ -72,8 +72,7 @@
"01208723ba84e15da2e71656544a2963b0c06d40" "01208723ba84e15da2e71656544a2963b0c06d40"
] ]
} }
], ]
"lostAccounts": []
}, },
"appState": { "appState": {
"menuOpen": false, "menuOpen": false,

@ -44,8 +44,7 @@
"01208723ba84e15da2e71656544a2963b0c06d40" "01208723ba84e15da2e71656544a2963b0c06d40"
] ]
} }
], ]
"lostAccounts": []
}, },
"appState": { "appState": {
"menuOpen": false, "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", "rinkeby": "ok",
"ropsten": "ok", "ropsten": "ok",
"goerli": "ok" "goerli": "ok"
}, }
"lostAccounts": []
}, },
"appState": { "appState": {
"shouldClose": false, "shouldClose": false,

@ -86,7 +86,6 @@
"type": "testnet" "type": "testnet"
}, },
"shapeShiftTxList": [], "shapeShiftTxList": [],
"lostAccounts": [],
"seedWords": null "seedWords": null
}, },
"appState": { "appState": {

@ -707,8 +707,7 @@
"rinkeby": "ok", "rinkeby": "ok",
"goerli": "ok" "goerli": "ok"
}, },
"shapeShiftTxList": [], "shapeShiftTxList": []
"lostAccounts": []
}, },
"appState": { "appState": {
"shouldClose": true, "shouldClose": true,

@ -78,8 +78,7 @@
"provider": { "provider": {
"type": "testnet" "type": "testnet"
}, },
"shapeShiftTxList": [], "shapeShiftTxList": []
"lostAccounts": []
}, },
"appState": { "appState": {
"menuOpen": false, "menuOpen": false,

@ -48,7 +48,6 @@
"type": "testnet" "type": "testnet"
}, },
"shapeShiftTxList": [], "shapeShiftTxList": [],
"lostAccounts": [],
"seedWords": null "seedWords": null
}, },
"appState": { "appState": {

@ -48,7 +48,6 @@
"type": "testnet" "type": "testnet"
}, },
"shapeShiftTxList": [], "shapeShiftTxList": [],
"lostAccounts": [],
"seedWords": null "seedWords": null
}, },
"appState": { "appState": {

@ -128,7 +128,6 @@
"type": "testnet" "type": "testnet"
}, },
"shapeShiftTxList": [], "shapeShiftTxList": [],
"lostAccounts": [],
"send": { "send": {
"gasLimit": "0xea60", "gasLimit": "0xea60",
"gasPrice": "0xba43b7400", "gasPrice": "0xba43b7400",

@ -28,6 +28,7 @@
"conversionRate": 1200.88200327, "conversionRate": 1200.88200327,
"conversionDate": 1489013762, "conversionDate": 1489013762,
"noActiveNotices": true, "noActiveNotices": true,
"incomingTransactions": {},
"frequentRpcList": [], "frequentRpcList": [],
"network": "3", "network": "3",
"accounts": { "accounts": {
@ -96,7 +97,6 @@
"type": "testnet" "type": "testnet"
}, },
"shapeShiftTxList": [], "shapeShiftTxList": [],
"lostAccounts": [],
"send": { "send": {
"gasLimit": null, "gasLimit": null,
"gasPrice": null, "gasPrice": null,

@ -86,7 +86,6 @@
"type": "testnet" "type": "testnet"
}, },
"shapeShiftTxList": [], "shapeShiftTxList": [],
"lostAccounts": [],
"frequentRpcListDetail": [] "frequentRpcListDetail": []
}, },
"appState": { "appState": {

@ -64,6 +64,7 @@
], ],
"tokens": [], "tokens": [],
"transactions": {}, "transactions": {},
"incomingTransactions": {},
"selectedAddressTxList": [ "selectedAddressTxList": [
{ {
"err": { "err": {
@ -1053,7 +1054,6 @@
{"depositAddress":"34vJ3AfmNcLiziA4VFgEVcQTwxVLD1qkke","depositType":"BTC","key":"shapeshift","response":{"status":"no_deposits","address":"34vJ3AfmNcLiziA4VFgEVcQTwxVLD1qkke"},"time":1522347459106}, {"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} {"depositAddress":"34vJ3AfmNcLiziA4VFgEVcQTwxVLD1qkkq","depositType":"BTC","key":"shapeshift","response":{"status":"no_deposits","address":"34vJ3AfmNcLiziA4VFgEVcQTwxVLD1qkkq"},"time":1522345459106}
], ],
"lostAccounts": [],
"send": {}, "send": {},
"currentLocale": "en", "currentLocale": "en",
"preferences": { "preferences": {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 138 KiB

@ -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:chrome": "SELENIUM_BROWSER=chrome test/e2e/run-web3.sh",
"test:web3:firefox": "SELENIUM_BROWSER=firefox 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: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: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": "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", "test:flat:build": "yarn test:flat:build:ui && yarn test:flat:build:tests && yarn test:flat:build:locales",

@ -64,7 +64,6 @@
"noActiveNotices": true, "noActiveNotices": true,
"shapeShiftTxList": [], "shapeShiftTxList": [],
"infuraNetworkStatus": {}, "infuraNetworkStatus": {},
"lostAccounts": [],
"seedWords": "debris dizzy just program just float decrease vacant alarm reduce speak stadium", "seedWords": "debris dizzy just program just float decrease vacant alarm reduce speak stadium",
"forgottenPassword": null "forgottenPassword": null
} }

@ -1,5 +1,8 @@
{ {
"metamask": { "metamask": {
"featureFlags": {
"showIncomingTransactions": true
},
"network": "4", "network": "4",
"identities": { "identities": {
"0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": { "0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc": {
@ -12,6 +15,7 @@
} }
}, },
"cachedBalances": {}, "cachedBalances": {},
"incomingTransactions": {},
"unapprovedTxs": { "unapprovedTxs": {
"8393540981007587": { "8393540981007587": {
"id": 8393540981007587, "id": 8393540981007587,

@ -1247,7 +1247,9 @@ describe('MetaMask', function () {
const transferTokens = await findElement(driver, By.xpath(`//button[contains(text(), 'Approve Tokens')]`)) const transferTokens = await findElement(driver, By.xpath(`//button[contains(text(), 'Approve Tokens')]`))
await transferTokens.click() 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 driver.switchTo().window(extension)
await delay(regularDelayMs) await delay(regularDelayMs)
@ -1341,6 +1343,10 @@ describe('MetaMask', function () {
}) })
it('finds the transaction in the transactions list', async function () { it('finds the transaction in the transactions list', async function () {
if (process.env.SELENIUM_BROWSER === 'firefox') {
this.skip()
}
await driver.wait(async () => { await driver.wait(async () => {
const confirmedTxes = await findElements(driver, By.css('.transaction-list__completed-transactions .transaction-list-item')) const confirmedTxes = await findElements(driver, By.css('.transaction-list__completed-transactions .transaction-list-item'))
return confirmedTxes.length === 3 return confirmedTxes.length === 3
@ -1354,6 +1360,12 @@ describe('MetaMask', function () {
}) })
describe('Tranfers a custom token from dapp when no gas value is specified', () => { 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 () => { it('transfers an already created token, without specifying gas', async () => {
const windowHandles = await driver.getAllWindowHandles() const windowHandles = await driver.getAllWindowHandles()
const extension = windowHandles[0] const extension = windowHandles[0]
@ -1403,6 +1415,12 @@ describe('MetaMask', function () {
}) })
describe('Approves a custom token from dapp when no gas value is specified', () => { 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 () => { it('approves an already created token', async () => {
const windowHandles = await driver.getAllWindowHandles() const windowHandles = await driver.getAllWindowHandles()
const extension = windowHandles[0] const extension = windowHandles[0]

@ -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 () { describe('#markPasswordForgotten', function () {
it('adds and sets forgottenPassword to config data to true', function () { it('adds and sets forgottenPassword to config data to true', function () {
metamaskController.markPasswordForgotten(noop) metamaskController.markPasswordForgotten(noop)

@ -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'))
})
})
})

@ -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, fromName: PropTypes.string,
toAddress: PropTypes.string, toAddress: PropTypes.string,
toName: PropTypes.string, toName: PropTypes.string,
toNickname: PropTypes.string,
// Content // Content
contentComponent: PropTypes.node, contentComponent: PropTypes.node,
errorKey: PropTypes.string, errorKey: PropTypes.string,
@ -68,6 +69,7 @@ export default class ConfirmPageContainer extends Component {
fromName, fromName,
fromAddress, fromAddress,
toName, toName,
toNickname,
toAddress, toAddress,
disabled, disabled,
errorKey, errorKey,
@ -126,6 +128,7 @@ export default class ConfirmPageContainer extends Component {
senderAddress={fromAddress} senderAddress={fromAddress}
recipientName={toName} recipientName={toName}
recipientAddress={toAddress} recipientAddress={toAddress}
recipientNickname={toNickname}
assetImage={renderAssetImage ? assetImage : undefined} assetImage={renderAssetImage ? assetImage : undefined}
/> />
</ConfirmPageContainerHeader> </ConfirmPageContainerHeader>

@ -215,7 +215,7 @@ SignatureRequest.prototype.msgHexToText = function (hex) {
} }
// eslint-disable-next-line react/display-name // eslint-disable-next-line react/display-name
SignatureRequest.prototype.renderTypedDataV3 = function (data) { SignatureRequest.prototype.renderTypedData = function (data) {
const { domain, message } = JSON.parse(data) const { domain, message } = JSON.parse(data)
return [ return [
h('div.request-signature__typed-container', [ h('div.request-signature__typed-container', [
@ -267,17 +267,18 @@ SignatureRequest.prototype.renderBody = function () {
}), }),
}, [notice]), }, [notice]),
h('div.request-signature__rows', type === 'eth_signTypedData' && version === 'V3' ? h('div.request-signature__rows',
this.renderTypedDataV3(data) : type === 'eth_signTypedData' && (version === 'V3' || version === 'V4') ?
rows.map(({ name, value }) => { this.renderTypedData(data) :
if (typeof value === 'boolean') { rows.map(({ name, value }) => {
value = value.toString() if (typeof value === 'boolean') {
} value = value.toString()
return h('div.request-signature__row', [ }
h('div.request-signature__row-title', [`${name}:`]), return h('div.request-signature__row', [
h('div.request-signature__row-value', value), h('div.request-signature__row-title', [`${name}:`]),
]) h('div.request-signature__row-value', value),
}), ])
}),
), ),
]) ])
} }

@ -12,6 +12,9 @@
} }
&__value { &__value {
display: flex;
justify-content: flex-end;
text-align: end; text-align: end;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;

@ -36,6 +36,7 @@ export default class TransactionListItem extends PureComponent {
rpcPrefs: PropTypes.object, rpcPrefs: PropTypes.object,
data: PropTypes.string, data: PropTypes.string,
getContractMethodData: PropTypes.func, getContractMethodData: PropTypes.func,
isDeposit: PropTypes.bool,
} }
static defaultProps = { static defaultProps = {
@ -117,7 +118,7 @@ export default class TransactionListItem extends PureComponent {
} }
renderPrimaryCurrency () { renderPrimaryCurrency () {
const { token, primaryTransaction: { txParams: { data } = {} } = {}, value } = this.props const { token, primaryTransaction: { txParams: { data } = {} } = {}, value, isDeposit } = this.props
return token return token
? ( ? (
@ -132,7 +133,7 @@ export default class TransactionListItem extends PureComponent {
className="transaction-list-item__amount transaction-list-item__amount--primary" className="transaction-list-item__amount transaction-list-item__amount--primary"
value={value} value={value}
type={PRIMARY} type={PRIMARY}
prefix="-" prefix={isDeposit ? '' : '-'}
/> />
) )
} }

@ -21,8 +21,10 @@ const mapStateToProps = (state, ownProps) => {
const { showFiatInTestnets } = preferencesSelector(state) const { showFiatInTestnets } = preferencesSelector(state)
const isMainnet = getIsMainnet(state) const isMainnet = getIsMainnet(state)
const { transactionGroup: { primaryTransaction } = {} } = ownProps const { transactionGroup: { primaryTransaction } = {} } = ownProps
const { txParams: { gas: gasLimit, gasPrice, data } = {} } = primaryTransaction const { txParams: { gas: gasLimit, gasPrice, data, to } = {} } = primaryTransaction
const selectedAccountBalance = accounts[getSelectedAddress(state)].balance const selectedAddress = getSelectedAddress(state)
const selectedAccountBalance = accounts[selectedAddress].balance
const isDeposit = selectedAddress === to
const selectRpcInfo = frequentRpcListDetail.find(rpcInfo => rpcInfo.rpcUrl === provider.rpcTarget) const selectRpcInfo = frequentRpcListDetail.find(rpcInfo => rpcInfo.rpcUrl === provider.rpcTarget)
const { rpcPrefs } = selectRpcInfo || {} const { rpcPrefs } = selectRpcInfo || {}
@ -42,6 +44,7 @@ const mapStateToProps = (state, ownProps) => {
selectedAccountBalance, selectedAccountBalance,
hasEnoughCancelGas, hasEnoughCancelGas,
rpcPrefs, rpcPrefs,
isDeposit,
} }
} }
@ -68,12 +71,13 @@ const mapDispatchToProps = dispatch => {
const mergeProps = (stateProps, dispatchProps, ownProps) => { const mergeProps = (stateProps, dispatchProps, ownProps) => {
const { transactionGroup: { primaryTransaction, initialTransaction } = {} } = ownProps const { transactionGroup: { primaryTransaction, initialTransaction } = {} } = ownProps
const { isDeposit } = stateProps
const { retryTransaction, ...restDispatchProps } = dispatchProps const { retryTransaction, ...restDispatchProps } = dispatchProps
const { txParams: { nonce, data } = {}, time } = initialTransaction const { txParams: { nonce, data } = {}, time = 0 } = initialTransaction
const { txParams: { value } = {} } = primaryTransaction const { txParams: { value } = {} } = primaryTransaction
const tokenData = data && getTokenData(data) 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 { return {
...stateProps, ...stateProps,

@ -19,6 +19,7 @@ export default class SenderToRecipient extends PureComponent {
senderAddress: PropTypes.string, senderAddress: PropTypes.string,
recipientName: PropTypes.string, recipientName: PropTypes.string,
recipientAddress: PropTypes.string, recipientAddress: PropTypes.string,
recipientNickname: PropTypes.string,
t: PropTypes.func, t: PropTypes.func,
variant: PropTypes.oneOf([DEFAULT_VARIANT, CARDS_VARIANT, FLAT_VARIANT]), variant: PropTypes.oneOf([DEFAULT_VARIANT, CARDS_VARIANT, FLAT_VARIANT]),
addressOnly: PropTypes.bool, addressOnly: PropTypes.bool,
@ -88,7 +89,7 @@ export default class SenderToRecipient extends PureComponent {
renderRecipientWithAddress () { renderRecipientWithAddress () {
const { t } = this.context const { t } = this.context
const { recipientName, recipientAddress, addressOnly, onRecipientClick } = this.props const { recipientName, recipientAddress, recipientNickname, addressOnly, onRecipientClick } = this.props
const checksummedRecipientAddress = checksumAddress(recipientAddress) const checksummedRecipientAddress = checksumAddress(recipientAddress)
return ( return (
@ -114,7 +115,7 @@ export default class SenderToRecipient extends PureComponent {
{ {
addressOnly addressOnly
? `${t('to')}: ${checksummedRecipientAddress}` ? `${t('to')}: ${checksummedRecipientAddress}`
: (recipientName || this.context.t('newContract')) : (recipientNickname || recipientName || this.context.t('newContract'))
} }
</div> </div>
</Tooltip> </Tooltip>

@ -3,6 +3,7 @@ const UNLOCK_ROUTE = '/unlock'
const LOCK_ROUTE = '/lock' const LOCK_ROUTE = '/lock'
const SETTINGS_ROUTE = '/settings' const SETTINGS_ROUTE = '/settings'
const GENERAL_ROUTE = '/settings/general' const GENERAL_ROUTE = '/settings/general'
const CONNECTIONS_ROUTE = '/settings/connections'
const ADVANCED_ROUTE = '/settings/advanced' const ADVANCED_ROUTE = '/settings/advanced'
const SECURITY_ROUTE = '/settings/security' const SECURITY_ROUTE = '/settings/security'
const ABOUT_US_ROUTE = '/settings/about-us' const ABOUT_US_ROUTE = '/settings/about-us'
@ -82,6 +83,7 @@ module.exports = {
ADVANCED_ROUTE, ADVANCED_ROUTE,
SECURITY_ROUTE, SECURITY_ROUTE,
GENERAL_ROUTE, GENERAL_ROUTE,
CONNECTIONS_ROUTE,
ABOUT_US_ROUTE, ABOUT_US_ROUTE,
CONTACT_LIST_ROUTE, CONTACT_LIST_ROUTE,
CONTACT_EDIT_ROUTE, CONTACT_EDIT_ROUTE,
@ -93,4 +95,3 @@ module.exports = {
NETWORKS_ROUTE, NETWORKS_ROUTE,
INITIALIZE_BACKUP_SEED_PHRASE_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 SIGNATURE_REQUEST_KEY = 'signatureRequest'
export const CONTRACT_INTERACTION_KEY = 'contractInteraction' export const CONTRACT_INTERACTION_KEY = 'contractInteraction'
export const CANCEL_ATTEMPT_ACTION_KEY = 'cancelAttempt' export const CANCEL_ATTEMPT_ACTION_KEY = 'cancelAttempt'
export const DEPOSIT_TRANSACTION_KEY = 'deposit'
export const TRANSACTION_TYPE_SHAPESHIFT = 'shapeshift' export const TRANSACTION_TYPE_SHAPESHIFT = 'shapeshift'

@ -21,6 +21,7 @@ import {
SIGNATURE_REQUEST_KEY, SIGNATURE_REQUEST_KEY,
CONTRACT_INTERACTION_KEY, CONTRACT_INTERACTION_KEY,
CANCEL_ATTEMPT_ACTION_KEY, CANCEL_ATTEMPT_ACTION_KEY,
DEPOSIT_TRANSACTION_KEY,
} from '../constants/transactions' } from '../constants/transactions'
import log from 'loglevel' import log from 'loglevel'
@ -124,6 +125,10 @@ export function isTokenMethodAction (transactionCategory) {
export function getTransactionActionKey (transaction) { export function getTransactionActionKey (transaction) {
const { msgParams, type, transactionCategory } = transaction const { msgParams, type, transactionCategory } = transaction
if (transactionCategory === 'incoming') {
return DEPOSIT_TRANSACTION_KEY
}
if (type === 'cancel') { if (type === 'cancel') {
return CANCEL_ATTEMPT_ACTION_KEY return CANCEL_ATTEMPT_ACTION_KEY
} }

@ -59,6 +59,7 @@ export default class ConfirmTransactionBase extends Component {
tokenData: PropTypes.object, tokenData: PropTypes.object,
tokenProps: PropTypes.object, tokenProps: PropTypes.object,
toName: PropTypes.string, toName: PropTypes.string,
toNickname: PropTypes.string,
transactionStatus: PropTypes.string, transactionStatus: PropTypes.string,
txData: PropTypes.object, txData: PropTypes.object,
unapprovedTxCount: PropTypes.number, unapprovedTxCount: PropTypes.number,
@ -529,6 +530,7 @@ export default class ConfirmTransactionBase extends Component {
fromAddress, fromAddress,
toName, toName,
toAddress, toAddress,
toNickname,
methodData, methodData,
valid: propsValid = true, valid: propsValid = true,
errorMessage, errorMessage,
@ -551,13 +553,13 @@ export default class ConfirmTransactionBase extends Component {
const { name } = methodData const { name } = methodData
const { valid, errorKey } = this.getErrorKey() const { valid, errorKey } = this.getErrorKey()
const { totalTx, positionOfCurrentTx, nextTxId, prevTxId, showNavigation, firstTx, lastTx, ofText, requestsWaitingText } = this.getNavigateTxData() const { totalTx, positionOfCurrentTx, nextTxId, prevTxId, showNavigation, firstTx, lastTx, ofText, requestsWaitingText } = this.getNavigateTxData()
return ( return (
<ConfirmPageContainer <ConfirmPageContainer
fromName={fromName} fromName={fromName}
fromAddress={fromAddress} fromAddress={fromAddress}
toName={toName} toName={toName}
toAddress={toAddress} toAddress={toAddress}
toNickname={toNickname}
showEdit={onEdit && !isTxReprice} showEdit={onEdit && !isTxReprice}
// In the event that the key is falsy (and inherently invalid), use a fallback string // 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')} action={getMethodName(name) || this.context.tOrKey(transactionCategory) || this.context.t('contractInteraction')}

@ -37,6 +37,7 @@ const mapStateToProps = (state, ownProps) => {
const { const {
conversionRate, conversionRate,
identities, identities,
addressBook,
currentCurrency, currentCurrency,
selectedAddress, selectedAddress,
selectedAddressTxList, selectedAddressTxList,
@ -75,6 +76,8 @@ const mapStateToProps = (state, ownProps) => {
: addressSlicer(checksumAddress(toAddress)) : addressSlicer(checksumAddress(toAddress))
) )
const addressBookObject = addressBook[checksumAddress(toAddress)]
const toNickname = addressBookObject ? addressBookObject.name : ''
const isTxReprice = Boolean(lastGasPrice) const isTxReprice = Boolean(lastGasPrice)
const transactionStatus = transaction ? transaction.status : '' const transactionStatus = transaction ? transaction.status : ''
@ -115,6 +118,7 @@ const mapStateToProps = (state, ownProps) => {
fromName, fromName,
toAddress, toAddress,
toName, toName,
toNickname,
ethTransactionAmount, ethTransactionAmount,
ethTransactionFee, ethTransactionFee,
ethTransactionTotal, ethTransactionTotal,

@ -49,7 +49,7 @@ export default class EndOfFlowScreen extends PureComponent {
{ '• ' + t('endOfFlowMessage7') } { '• ' + t('endOfFlowMessage7') }
</div> </div>
<div className="first-time-flow__text-block end-of-flow__text-4"> <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" href="https://metamask.zendesk.com/hc/en-us/articles/360015489591-Basic-Safety-Tips"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"

@ -21,18 +21,10 @@ export default class Home extends PureComponent {
} }
static defaultProps = { static defaultProps = {
activeTab: {},
unsetMigratedPrivacyMode: null, unsetMigratedPrivacyMode: null,
forceApproveProviderRequestByOrigin: null,
} }
static propTypes = { static propTypes = {
activeTab: PropTypes.shape({
origin: PropTypes.string,
protocol: PropTypes.string,
title: PropTypes.string,
url: PropTypes.string,
}),
history: PropTypes.object, history: PropTypes.object,
forgottenPassword: PropTypes.bool, forgottenPassword: PropTypes.bool,
suggestedTokens: PropTypes.object, suggestedTokens: PropTypes.object,
@ -40,10 +32,7 @@ export default class Home extends PureComponent {
providerRequests: PropTypes.array, providerRequests: PropTypes.array,
showPrivacyModeNotification: PropTypes.bool.isRequired, showPrivacyModeNotification: PropTypes.bool.isRequired,
unsetMigratedPrivacyMode: PropTypes.func, unsetMigratedPrivacyMode: PropTypes.func,
viewingUnconnectedDapp: PropTypes.bool.isRequired,
forceApproveProviderRequestByOrigin: PropTypes.func,
shouldShowSeedPhraseReminder: PropTypes.bool, shouldShowSeedPhraseReminder: PropTypes.bool,
rejectProviderRequestByOrigin: PropTypes.func,
isPopup: PropTypes.bool, isPopup: PropTypes.bool,
} }
@ -73,16 +62,12 @@ export default class Home extends PureComponent {
render () { render () {
const { t } = this.context const { t } = this.context
const { const {
activeTab,
forgottenPassword, forgottenPassword,
providerRequests, providerRequests,
history, history,
showPrivacyModeNotification, showPrivacyModeNotification,
unsetMigratedPrivacyMode, unsetMigratedPrivacyMode,
viewingUnconnectedDapp,
forceApproveProviderRequestByOrigin,
shouldShowSeedPhraseReminder, shouldShowSeedPhraseReminder,
rejectProviderRequestByOrigin,
isPopup, isPopup,
} = this.props } = this.props
@ -120,20 +105,6 @@ export default class Home extends PureComponent {
key="home-privacyModeDefault" 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, shouldBeRendered: shouldShowSeedPhraseReminder,
component: <HomeNotification component: <HomeNotification

@ -5,51 +5,31 @@ import { withRouter } from 'react-router-dom'
import { unconfirmedTransactionsCountSelector } from '../../selectors/confirm-transaction' import { unconfirmedTransactionsCountSelector } from '../../selectors/confirm-transaction'
import { getCurrentEthBalance } from '../../selectors/selectors' import { getCurrentEthBalance } from '../../selectors/selectors'
import { import {
forceApproveProviderRequestByOrigin,
unsetMigratedPrivacyMode, unsetMigratedPrivacyMode,
rejectProviderRequestByOrigin,
} from '../../store/actions' } from '../../store/actions'
import { getEnvironmentType } from '../../../../app/scripts/lib/util' import { getEnvironmentType } from '../../../../app/scripts/lib/util'
import { ENVIRONMENT_TYPE_POPUP } from '../../../../app/scripts/lib/enums' import { ENVIRONMENT_TYPE_POPUP } from '../../../../app/scripts/lib/enums'
const activeTabDappProtocols = ['http:', 'https:', 'dweb:', 'ipfs:', 'ipns:', 'ssb:']
const mapStateToProps = state => { const mapStateToProps = state => {
const { activeTab, metamask, appState } = state const { metamask, appState } = state
const { const {
approvedOrigins,
dismissedOrigins,
lostAccounts,
suggestedTokens, suggestedTokens,
providerRequests, providerRequests,
migratedPrivacyMode, migratedPrivacyMode,
featureFlags: {
privacyMode,
} = {},
seedPhraseBackedUp, seedPhraseBackedUp,
tokens, tokens,
} = metamask } = metamask
const accountBalance = getCurrentEthBalance(state) const accountBalance = getCurrentEthBalance(state)
const { forgottenPassword } = appState 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 const isPopup = getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP
return { return {
lostAccounts,
forgottenPassword, forgottenPassword,
suggestedTokens, suggestedTokens,
unconfirmedTransactionsCount: unconfirmedTransactionsCountSelector(state), unconfirmedTransactionsCount: unconfirmedTransactionsCountSelector(state),
providerRequests, providerRequests,
showPrivacyModeNotification: migratedPrivacyMode, showPrivacyModeNotification: migratedPrivacyMode,
activeTab,
viewingUnconnectedDapp: isUnconnected && isPopup,
shouldShowSeedPhraseReminder: !seedPhraseBackedUp && (parseInt(accountBalance, 16) > 0 || tokens.length > 0), shouldShowSeedPhraseReminder: !seedPhraseBackedUp && (parseInt(accountBalance, 16) > 0 || tokens.length > 0),
isPopup, isPopup,
} }
@ -57,8 +37,6 @@ const mapStateToProps = state => {
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
unsetMigratedPrivacyMode: () => dispatch(unsetMigratedPrivacyMode()), unsetMigratedPrivacyMode: () => dispatch(unsetMigratedPrivacyMode()),
forceApproveProviderRequestByOrigin: (origin) => dispatch(forceApproveProviderRequestByOrigin(origin)),
rejectProviderRequestByOrigin: origin => dispatch(rejectProviderRequestByOrigin(origin)),
}) })
export default compose( export default compose(

@ -30,10 +30,10 @@ export default class EnsInput extends Component {
updateEnsResolution: PropTypes.func, updateEnsResolution: PropTypes.func,
scanQrCode: PropTypes.func, scanQrCode: PropTypes.func,
updateEnsResolutionError: PropTypes.func, updateEnsResolutionError: PropTypes.func,
addressBook: PropTypes.array,
onPaste: PropTypes.func, onPaste: PropTypes.func,
onReset: PropTypes.func, onReset: PropTypes.func,
onValidAddressTyped: PropTypes.func, onValidAddressTyped: PropTypes.func,
contact: PropTypes.object,
} }
state = { state = {
@ -181,8 +181,7 @@ export default class EnsInput extends Component {
renderSelected () { renderSelected () {
const { t } = this.context const { t } = this.context
const { className, selectedAddress, selectedName, addressBook } = this.props const { className, selectedAddress, selectedName, contact = {} } = this.props
const contact = addressBook.filter(item => item.address === selectedAddress)[0] || {}
const name = contact.name || selectedName const name = contact.name || selectedName

@ -5,16 +5,19 @@ import {
getSendToNickname, getSendToNickname,
} from '../../send.selectors' } from '../../send.selectors'
import { import {
getAddressBook, getAddressBookEntry,
} from '../../../../selectors/selectors' } from '../../../../selectors/selectors'
const connect = require('react-redux').connect const connect = require('react-redux').connect
export default connect( export default connect(
state => ({ state => {
network: getCurrentNetwork(state), const selectedAddress = getSendTo(state)
selectedAddress: getSendTo(state), return {
selectedName: getSendToNickname(state), network: getCurrentNetwork(state),
addressBook: getAddressBook(state), selectedAddress,
}) selectedName: getSendToNickname(state),
contact: getAddressBookEntry(state, selectedAddress),
}
}
)(EnsInput) )(EnsInput)

@ -18,9 +18,10 @@ export default class SendContent extends Component {
scanQrCode: PropTypes.func, scanQrCode: PropTypes.func,
showAddToAddressBookModal: PropTypes.func, showAddToAddressBookModal: PropTypes.func,
showHexData: PropTypes.bool, showHexData: PropTypes.bool,
to: PropTypes.string,
ownedAccounts: PropTypes.array, ownedAccounts: PropTypes.array,
addressBook: PropTypes.array, addressBook: PropTypes.array,
contact: PropTypes.object,
isOwnedAccount: PropTypes.bool,
} }
updateGas = (updateData) => this.props.updateGas(updateData) updateGas = (updateData) => this.props.updateGas(updateData)
@ -47,9 +48,7 @@ export default class SendContent extends Component {
maybeRenderAddContact () { maybeRenderAddContact () {
const { t } = this.context const { t } = this.context
const { to, addressBook = [], ownedAccounts = [], showAddToAddressBookModal } = this.props const { isOwnedAccount, showAddToAddressBookModal, contact = {} } = this.props
const isOwnedAccount = !!ownedAccounts.find(({ address }) => address.toLowerCase() === to.toLowerCase())
const contact = addressBook.find(({ address }) => address === to) || {}
if (isOwnedAccount || contact.name) { if (isOwnedAccount || contact.name) {
return return

@ -5,15 +5,17 @@ import {
getSendTo, getSendTo,
} from '../send.selectors' } from '../send.selectors'
import { import {
getAddressBook, getAddressBookEntry,
} from '../../../selectors/selectors' } from '../../../selectors/selectors'
import actions from '../../../store/actions' import actions from '../../../store/actions'
function mapStateToProps (state) { function mapStateToProps (state) {
const ownedAccounts = accountsWithSendEtherInfoSelector(state)
const to = getSendTo(state)
return { return {
to: getSendTo(state), isOwnedAccount: !!ownedAccounts.find(({ address }) => address.toLowerCase() === to.toLowerCase()),
addressBook: getAddressBook(state), contact: getAddressBookEntry(state, to),
ownedAccounts: accountsWithSendEtherInfoSelector(state), to,
} }
} }

@ -52,11 +52,10 @@ describe('SendContent Component', function () {
assert.equal(PageContainerContentChild.childAt(4).exists(), false) 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({ wrapper.setProps({
showHexData: false, showHexData: false,
to: '0x80F061544cC398520615B5d3e7A3BedD70cd4510', contact: { name: 'testName' },
addressBook: [{ address: '0x80F061544cC398520615B5d3e7A3BedD70cd4510', name: 'dinodan' }],
}) })
const PageContainerContentChild = wrapper.find(PageContainerContent).children() const PageContainerContentChild = wrapper.find(PageContainerContent).children()
assert(PageContainerContentChild.childAt(0).is(SendAssetRow), 'row[1] should be SendAssetRow') 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) 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({ wrapper.setProps({
showHexData: false, showHexData: false,
to: '0x80F061544cC398520615B5d3e7A3BedD70cd4510', isOwnedAccount: true,
addressBook: [],
ownedAccounts: [{ address: '0x80F061544cC398520615B5d3e7A3BedD70cd4510', name: 'dinodan' }],
}) })
const PageContainerContentChild = wrapper.find(PageContainerContent).children() const PageContainerContentChild = wrapper.find(PageContainerContent).children()
assert(PageContainerContentChild.childAt(0).is(SendAssetRow), 'row[1] should be SendAssetRow') assert(PageContainerContentChild.childAt(0).is(SendAssetRow), 'row[1] should be SendAssetRow')

@ -38,6 +38,7 @@ proxyquire('../send-footer.container.js', {
getSendEditingTransactionId: (s) => `mockEditingTransactionId:${s}`, getSendEditingTransactionId: (s) => `mockEditingTransactionId:${s}`,
getSendFromObject: (s) => `mockFromObject:${s}`, getSendFromObject: (s) => `mockFromObject:${s}`,
getSendTo: (s) => `mockTo:${s}`, getSendTo: (s) => `mockTo:${s}`,
getSendToNickname: (s) => `mockToNickname:${s}`,
getSendToAccounts: (s) => `mockToAccounts:${s}`, getSendToAccounts: (s) => `mockToAccounts:${s}`,
getTokenBalance: (s) => `mockTokenBalance:${s}`, getTokenBalance: (s) => `mockTokenBalance:${s}`,
getSendHexData: (s) => `mockHexData:${s}`, getSendHexData: (s) => `mockHexData:${s}`,

@ -157,7 +157,6 @@ module.exports = {
{ id: 'shapeShiftTx2', 'time': 1575000000000 }, { id: 'shapeShiftTx2', 'time': 1575000000000 },
{ id: 'shapeShiftTx3', 'time': 1475000000000 }, { id: 'shapeShiftTx3', 'time': 1475000000000 },
], ],
'lostAccounts': [],
'send': { 'send': {
'gasLimit': '0xFFFF', 'gasLimit': '0xFFFF',
'gasPrice': '0xaa', '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;
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save