Merge pull request #5539 from MetaMask/v4.16.0

Version 4.16.0
feature/default_network_editable
kumavis 6 years ago committed by GitHub
commit c2f97717c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 24
      CHANGELOG.md
  2. 2
      README.md
  3. 24
      app/_locales/en/messages.json
  4. 14
      app/_locales/es/messages.json
  5. 1
      app/_locales/index.json
  6. 485
      app/_locales/it/messages.json
  7. 90
      app/_locales/ko/messages.json
  8. 1213
      app/_locales/pl/messages.json
  9. 14
      app/images/eth.svg
  10. 2
      app/manifest.json
  11. 3
      app/scripts/background.js
  12. 19
      app/scripts/contentscript.js
  13. 30
      app/scripts/controllers/preferences.js
  14. 35
      app/scripts/controllers/transactions/index.js
  15. 5
      app/scripts/controllers/transactions/tx-state-manager.js
  16. 2
      app/scripts/inpage.js
  17. 34
      app/scripts/lib/setupFetchDebugging.js
  18. 7
      app/scripts/metamask-controller.js
  19. 5
      development/states/add-token.json
  20. 5
      development/states/confirm-sig-requests.json
  21. 5
      development/states/currency-localization.json
  22. 5
      development/states/first-time.json
  23. 5
      development/states/send-new-ui.json
  24. 5
      development/states/tx-list-items.json
  25. 202
      package-lock.json
  26. 4
      package.json
  27. 2
      test/e2e/beta/from-import-beta-ui.spec.js
  28. 16
      test/e2e/beta/metamask-beta-ui.spec.js
  29. 140
      test/integration/lib/add-token.js
  30. 20
      test/integration/lib/send-new-ui.js
  31. 44
      test/unit/components/balance-component-test.js
  32. 36
      ui/app/actions.js
  33. 11
      ui/app/components/account-menu/index.js
  34. 34
      ui/app/components/add-token-button/add-token-button.component.js
  35. 1
      ui/app/components/add-token-button/index.js
  36. 26
      ui/app/components/add-token-button/index.scss
  37. 28
      ui/app/components/balance-component.js
  38. 58
      ui/app/components/confirm-page-container/confirm-detail-row/confirm-detail-row.component.js
  39. 12
      ui/app/components/confirm-page-container/confirm-detail-row/index.scss
  40. 26
      ui/app/components/confirm-page-container/confirm-detail-row/tests/confirm-detail-row.component.test.js
  41. 7
      ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-content.component.js
  42. 19
      ui/app/components/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js
  43. 5
      ui/app/components/confirm-page-container/confirm-page-container.component.js
  44. 11
      ui/app/components/currency-display/currency-display.component.js
  45. 25
      ui/app/components/currency-display/currency-display.container.js
  46. 10
      ui/app/components/currency-display/index.scss
  47. 21
      ui/app/components/currency-display/tests/currency-display.container.test.js
  48. 120
      ui/app/components/currency-input/currency-input.component.js
  49. 27
      ui/app/components/currency-input/currency-input.container.js
  50. 1
      ui/app/components/currency-input/index.js
  51. 7
      ui/app/components/currency-input/index.scss
  52. 239
      ui/app/components/currency-input/tests/currency-input.component.test.js
  53. 55
      ui/app/components/currency-input/tests/currency-input.container.test.js
  54. 8
      ui/app/components/index.scss
  55. 12
      ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/cancel-transaction-gas-fee.component.js
  56. 9
      ui/app/components/modals/cancel-transaction/cancel-transaction-gas-fee/tests/cancel-transaction-gas-fee.component.test.js
  57. 57
      ui/app/components/pages/confirm-token-transaction-base/confirm-token-transaction-base.component.js
  58. 87
      ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.component.js
  59. 8
      ui/app/components/pages/confirm-transaction-base/confirm-transaction-base.container.js
  60. 18
      ui/app/components/pages/settings/settings-tab/index.scss
  61. 53
      ui/app/components/pages/settings/settings-tab/settings-tab.component.js
  62. 7
      ui/app/components/pages/settings/settings-tab/settings-tab.container.js
  63. 27
      ui/app/components/send/account-list-item/account-list-item.component.js
  64. 16
      ui/app/components/send/account-list-item/tests/account-list-item-component.test.js
  65. 186
      ui/app/components/send/currency-display/currency-display.js
  66. 1
      ui/app/components/send/currency-display/index.js
  67. 91
      ui/app/components/send/currency-display/tests/currency-display.test.js
  68. 22
      ui/app/components/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js
  69. 9
      ui/app/components/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js
  70. 45
      ui/app/components/send/send-content/send-amount-row/send-amount-row.component.js
  71. 27
      ui/app/components/send/send-content/send-amount-row/tests/send-amount-row-component.test.js
  72. 31
      ui/app/components/send/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js
  73. 12
      ui/app/components/send/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js
  74. 1
      ui/app/components/token-input/index.js
  75. 308
      ui/app/components/token-input/tests/token-input.component.test.js
  76. 129
      ui/app/components/token-input/tests/token-input.container.test.js
  77. 136
      ui/app/components/token-input/token-input.component.js
  78. 27
      ui/app/components/token-input/token-input.container.js
  79. 6
      ui/app/components/transaction-activity-log/transaction-activity-log.util.js
  80. 35
      ui/app/components/transaction-breakdown/transaction-breakdown.component.js
  81. 3
      ui/app/components/transaction-list-item-details/transaction-list-item-details.component.js
  82. 17
      ui/app/components/transaction-list-item/transaction-list-item.component.js
  83. 5
      ui/app/components/transaction-status/index.scss
  84. 4
      ui/app/components/transaction-view-balance/tests/token-view-balance.component.test.js
  85. 14
      ui/app/components/transaction-view-balance/transaction-view-balance.component.js
  86. 1
      ui/app/components/unit-input/index.js
  87. 44
      ui/app/components/unit-input/index.scss
  88. 146
      ui/app/components/unit-input/tests/unit-input.component.test.js
  89. 104
      ui/app/components/unit-input/unit-input.component.js
  90. 1
      ui/app/components/user-preferenced-currency-display/index.js
  91. 34
      ui/app/components/user-preferenced-currency-display/tests/user-preferenced-currency-display.component.test.js
  92. 105
      ui/app/components/user-preferenced-currency-display/tests/user-preferenced-currency-display.container.test.js
  93. 45
      ui/app/components/user-preferenced-currency-display/user-preferenced-currency-display.component.js
  94. 52
      ui/app/components/user-preferenced-currency-display/user-preferenced-currency-display.container.js
  95. 1
      ui/app/components/user-preferenced-currency-input/index.js
  96. 32
      ui/app/components/user-preferenced-currency-input/tests/user-preferenced-currency-input.component.test.js
  97. 31
      ui/app/components/user-preferenced-currency-input/tests/user-preferenced-currency-input.container.test.js
  98. 20
      ui/app/components/user-preferenced-currency-input/user-preferenced-currency-input.component.js
  99. 13
      ui/app/components/user-preferenced-currency-input/user-preferenced-currency-input.container.js
  100. 1
      ui/app/components/user-preferenced-token-input/index.js
  101. Some files were not shown because too many files have changed in this diff Show More

@ -2,19 +2,37 @@
## Current Develop Branch
## 4.16.0 Wednesday October 17 2018
- Feature: Add toggle for primary currency (eth/fiat)
- Feature: add tooltip for view etherscan tx
- Feature: add Polish translations
- Feature: improve Korean translations
- Feature: improve Italian translations
- Bug Fix: Fix bug with "pending" block reference
- Bug Fix: Force AccountTracker to update balances on network change
- Bug Fix: Fix document extension check when injecting web3
- Bug Fix: Fix some support links
## 4.15.0 Thursday October 11 2018
- A rollback release, equivalent to `v4.11.1` to be deployed in the case that `v4.14.0` is found to have bugs.
## 4.14.0 Thursday October 11 2018
- Update transaction statuses when switching networks.
- [#5470](https://github.com/MetaMask/metamask-extension/pull/5470) 100% coverage in French locale, fixed the procedure to verify proposed locale.
- Added rudimentary support for the subscription API to support web3 1.0 and Truffle's Drizzle.
- [#5502](https://github.com/MetaMask/metamask-extension/pull/5502) Update Italian translation.
## 4.12.0 Thursday September 27 2018
- Reintroduces changes from 4.10.0
## 4.13.0
- A rollback release, equivalent to `v4.11.1` to be deployed in the case that `v4.12.0` is found to have bugs.
## 4.12.0 Thursday September 27 2018
- Reintroduces changes from 4.10.0
## 4.11.1 Tuesday September 25 2018
- Adds Ledger support.

@ -1,5 +1,5 @@
# MetaMask Browser Extension
[![Build Status](https://circleci.com/gh/MetaMask/metamask-extension.svg?style=shield&circle-token=a1ddcf3cd38e29267f254c9c59d556d513e3a1fd)](https://circleci.com/gh/MetaMask/metamask-extension) [![Coverage Status](https://coveralls.io/repos/github/MetaMask/metamask-extension/badge.svg?branch=master)](https://coveralls.io/github/MetaMask/metamask-extension?branch=master) [![Greenkeeper badge](https://badges.greenkeeper.io/MetaMask/metamask-extension.svg)](https://greenkeeper.io/) [![Stories in Ready](https://badge.waffle.io/MetaMask/metamask-extension.png?label=in%20progress&title=waffle.io)](https://waffle.io/MetaMask/metamask-extension)
[![Build Status](https://circleci.com/gh/MetaMask/metamask-extension.svg?style=shield&circle-token=a1ddcf3cd38e29267f254c9c59d556d513e3a1fd)](https://circleci.com/gh/MetaMask/metamask-extension) [![Coverage Status](https://coveralls.io/repos/github/MetaMask/metamask-extension/badge.svg?branch=master)](https://coveralls.io/github/MetaMask/metamask-extension?branch=master) [![Stories in Ready](https://badge.waffle.io/MetaMask/metamask-extension.png?label=in%20progress&title=waffle.io)](https://waffle.io/MetaMask/metamask-extension)
## Support

@ -140,6 +140,9 @@
"clickCopy": {
"message": "Click to Copy"
},
"clickToAdd": {
"message": "Click on $1 to add them to your account"
},
"close": {
"message": "Close"
},
@ -361,6 +364,9 @@
"enterPasswordContinue": {
"message": "Enter password to continue"
},
"eth": {
"message": "ETH"
},
"etherscanView": {
"message": "View account on Etherscan"
},
@ -380,7 +386,7 @@
"message": "Failed"
},
"fiat": {
"message": "FIAT",
"message": "Fiat",
"description": "Exchange type"
},
"fileImportFail": {
@ -424,6 +430,9 @@
"gasLimitTooLow": {
"message": "Gas limit must be at least 21000"
},
"gasUsed": {
"message": "Gas Used"
},
"generatingSeed": {
"message": "Generating Seed..."
},
@ -635,6 +644,9 @@
"min": {
"message": "Minimum"
},
"missingYourTokens": {
"message": "Don't see your tokens?"
},
"myAccounts": {
"message": "My Accounts"
},
@ -787,6 +799,12 @@
"prev": {
"message": "Prev"
},
"primaryCurrencySetting": {
"message": "Primary Currency"
},
"primaryCurrencySettingDescription": {
"message": "Select ETH to prioritize displaying values in ETH. Select Fiat to prioritize displaying values in your selected currency."
},
"privacyMsg": {
"message": "Privacy Policy"
},
@ -795,7 +813,7 @@
"description": "select this type of file to use to import an account"
},
"privateKeyWarning": {
"message": "Warning: Never disclose this key. Anyone with your private keys can take steal any assets held in your account."
"message": "Warning: Never disclose this key. Anyone with your private keys can steal any assets held in your account."
},
"privateNetwork": {
"message": "Private Network"
@ -1186,7 +1204,7 @@
"message": "These 12 words are the only way to restore your MetaMask accounts.\nSave them somewhere safe and secret."
},
"typePassword": {
"message": "Type Your Password"
"message": "Type your MetaMask password"
},
"uiWelcome": {
"message": "Welcome to the New UI (Beta)"

@ -156,7 +156,7 @@
"message": " Copiar "
},
"copyPrivateKey": {
"message": "Ésta es tu llave privada (haz click para copiar)"
"message": "Ésta es tu clave privada (haz click para copiar)"
},
"copyToClipboard": {
"message": "Copiar al portapapeles"
@ -278,10 +278,10 @@
"message": "Tipo de cambio"
},
"exportPrivateKey": {
"message": "Exportar llave privada"
"message": "Exportar clave privada"
},
"exportPrivateKeyWarning": {
"message": "Exportar llaves privadas bajo TU PROPIO riesgo"
"message": "Exportar claves privadas bajo TU PROPIO riesgo"
},
"failed": {
"message": "Fallo"
@ -579,8 +579,8 @@
"description": "En el proceso de creación de contraseña, esta no es lo suficientemente larga para ser segura"
},
"pastePrivateKey": {
"message": "Pega tu llave privada aqui",
"description": "Para importar una cuenta desde una llave privada"
"message": "Pega tu clave privada aqui",
"description": "Para importar una cuenta desde una clave privada"
},
"pasteSeed": {
"message": "¡Pega tu frase semilla aquí!"
@ -595,7 +595,7 @@
"message": "Política de privacidad"
},
"privateKey": {
"message": "Llave privada",
"message": "Clave privada",
"description": "Selecciona este tupo de archivo para importar una cuenta"
},
"privateKeyWarning": {
@ -712,7 +712,7 @@
"message": "Comprar con ShapeShift"
},
"showPrivateKeys": {
"message": "Mostrar llaves privadas"
"message": "Mostrar claves privadas"
},
"showQRCode": {
"message": "Mostrar codigo QR"

@ -11,6 +11,7 @@
{ "code": "ko", "name": "Korean" },
{ "code": "nl", "name": "Dutch" },
{ "code": "ph", "name": "Tagalog" },
{ "code": "pl", "name": "Polish" },
{ "code": "pt", "name": "Portuguese" },
{ "code": "ru", "name": "Russian" },
{ "code": "sl", "name": "Slovenian" },

@ -2,6 +2,9 @@
"accept": {
"message": "Accetta"
},
"accessingYourCamera": {
"message": "Accesso alla fotocamera..."
},
"account": {
"message": "Account"
},
@ -11,6 +14,15 @@
"accountName": {
"message": "Nome Account"
},
"accountOptions": {
"message": "Account Options"
},
"accountSelectionRequired": {
"message": "Devi selezionare un account!"
},
"activityLog": {
"message": "log attività"
},
"address": {
"message": "Indirizzo"
},
@ -23,6 +35,12 @@
"addTokens": {
"message": "Aggiungi più token"
},
"addSuggestedTokens": {
"message": "Aggiungi Token Suggeriti"
},
"addAcquiredTokens": {
"message": "Aggiungi i token che hai acquistato usando MetaMask"
},
"amount": {
"message": "Importo"
},
@ -37,9 +55,21 @@
"message": "MetaMask",
"description": "Il nome dell'applicazione"
},
"approve": {
"message": "Approva"
},
"approved": {
"message": "Approvato"
},
"attemptingConnect": {
"message": "Tentativo di connessione alla blockchain."
},
"attemptToCancel": {
"message": "Tentativo di Annullamento?"
},
"attemptToCancelDescription": {
"message": "Tentare di annullare non garantisce che la transazione verrà annullata. Se annullata, è comunque richiesto pagare alla rete la commissione sulla transazione."
},
"attributions": {
"message": "Attribuzioni"
},
@ -71,8 +101,11 @@
"borrowDharma": {
"message": "Prendi in presisito con Dharma (Beta)"
},
"browserNotSupported": {
"message": "Il tuo Browser non è supportato..."
},
"builtInCalifornia": {
"message": "MetaMask è progettato e costruito in California."
"message": "MetaMask è progettato e realizzato in California."
},
"buy": {
"message": "Compra"
@ -83,8 +116,23 @@
"buyCoinbaseExplainer": {
"message": "Coinbase è il servizio più popolare al mondo per comprare e vendere Bitcoin, Ethereum e Litecoin."
},
"bytes": {
"message": "Bytes"
},
"ok": {
"message": "Ok"
},
"cancel": {
"message": "Cancella"
"message": "Annulla"
},
"cancelAttempt": {
"message": "Tentativo di Annullamento"
},
"cancellationGasFee": {
"message": "Commissione di Annullamento in Gas"
},
"cancelN": {
"message": "Cancel all $1 transactions"
},
"classicInterface": {
"message": "Usa l'interfaccia classica"
@ -92,9 +140,18 @@
"clickCopy": {
"message": "Clicca per Copiare"
},
"close": {
"message": "Chiudi"
},
"chromeRequiredForHardwareWallets": {
"message": "Devi usare MetaMask con Google Chrome per connettere il tuo Portafoglio Hardware"
},
"confirm": {
"message": "Conferma"
},
"confirmed": {
"message": "Confermata"
},
"confirmContract": {
"message": "Conferma Contratto"
},
@ -104,6 +161,36 @@
"confirmTransaction": {
"message": "Conferma Transazione"
},
"connectHardwareWallet": {
"message": "Connetti Portafoglio Hardware"
},
"connect": {
"message": "Connetti"
},
"connecting": {
"message": "Connessione..."
},
"connectingToKovan": {
"message": "Connessione alla Rete di test Kovan"
},
"connectingToMainnet": {
"message": "Connessione alla Rete Ethereum Principale"
},
"connectingToRopsten": {
"message": "Connessione alla Rete di test Ropsten"
},
"connectingToRinkeby": {
"message": "Connessione alla Rete di test Rinkeby"
},
"connectingToUnknown": {
"message": "Connessione ad una Rete Sconosciuta"
},
"connectToLedger": {
"message": "Connettersi al Ledger"
},
"connectToTrezor": {
"message": "Connettersi al Trezor"
},
"continue": {
"message": "Continua"
},
@ -131,6 +218,9 @@
"copy": {
"message": "Copia"
},
"copyAddress": {
"message": "Copia l'indirizzo"
},
"copyToClipboard": {
"message": "Copia negli appunti"
},
@ -156,12 +246,21 @@
"currentConversion": {
"message": "Cambio Corrente"
},
"currentLanguage": {
"message": "Lingua Corrente"
},
"currentNetwork": {
"message": "Rete Corrente"
},
"currentRpc": {
"message": "RPC Corrente"
},
"customGas": {
"message": "Personalizza Gas"
},
"customToken": {
"message": "Token Personalizzato"
},
"customize": {
"message": "Personalizza"
},
@ -223,33 +322,54 @@
"done": {
"message": "Finito"
},
"downloadGoogleChrome": {
"message": "Scarica Google Chrome"
},
"downloadStateLogs": {
"message": "Scarica i log di Stato"
},
"dontHaveAHardwareWallet": {
"message": "Non hai un portafoglio hardware?"
},
"dropped": {
"message": "Abbandonata"
},
"edit": {
"message": "Modifica"
},
"editAccountName": {
"message": "Modifica Nome Account"
},
"editingTransaction": {
"message": "Modifica la transazione"
},
"emailUs": {
"message": "Mandaci una mail!"
},
"encryptNewDen": {
"message": "Cripta il tuo nuovo DEN"
},
"ensNameNotFound": {
"message": "Nome ENS non trovato"
},
"enterPassword": {
"message": "Inserisci password"
},
"enterPasswordConfirm": {
"message": "Inserisci la tua password per confermare"
},
"enterPasswordContinue": {
"message": "Inserisci la tua password per continuare"
},
"etherscanView": {
"message": "Vedi account su Etherscan"
},
"exchangeRate": {
"message": "Tasso di cambio"
},
"expandView": {
"message": "Expand View"
},
"exportPrivateKey": {
"message": "Esporta Chiave Privata"
},
@ -257,7 +377,7 @@
"message": "Esporta chiave privata a tuo rischio."
},
"failed": {
"message": "Fallito"
"message": "Fallita"
},
"fiat": {
"message": "FIAT",
@ -270,6 +390,9 @@
"followTwitter": {
"message": "Seguici su Twitter"
},
"forgetDevice": {
"message": "Dimentica questo dispositivo"
},
"from": {
"message": "Da"
},
@ -279,6 +402,9 @@
"fromShapeShift": {
"message": "Da ShapeShift"
},
"functionType": {
"message": "Tipo della Funzione"
},
"gas": {
"message": "Gas",
"description": "Piccola indicazione del costo del gas"
@ -310,6 +436,9 @@
"gasPriceRequired": {
"message": "Prezzo Gas Richiesto"
},
"generatingTransaction": {
"message": "Generando la transazione"
},
"getEther": {
"message": "Ottieni Ether"
},
@ -317,10 +446,28 @@
"message": "Ottieni Get Ether da un faucet per $1",
"description": "Visualizza il nome della rete per il faucet Ether"
},
"getHelp": {
"message": "Aiuto."
},
"greaterThanMin": {
"message": "deve essere maggiore o uguale a $1.",
"description": "aiuto per inserire un input esadecimale come decimale"
},
"hardware": {
"message": "hardware"
},
"hardwareWalletConnected": {
"message": "Portafoglio hardware connesso"
},
"hardwareWallets": {
"message": "Connetti portafoglio hardware"
},
"hardwareWalletsMsg": {
"message": "Selezione un portafoglio hardware che vuoi utilizzare con MetaMask"
},
"havingTroubleConnecting": {
"message": "Problemi di connessione?"
},
"here": {
"message": "qui",
"description": "per intendere -clicca qui- per maggiori informazioni (va con troubleTokenBalances)"
@ -328,6 +475,9 @@
"hereList": {
"message": "Questa è una lista!!!!"
},
"hexData": {
"message": "Dati Hex"
},
"hide": {
"message": "Nascondi"
},
@ -337,6 +487,9 @@
"hideTokenPrompt": {
"message": "Nascondi Token?"
},
"history": {
"message": "Storico"
},
"howToDeposit": {
"message": "Come vuoi depositare Ether?"
},
@ -363,9 +516,18 @@
"message": "Importato",
"description": "stato che conferma che un account è stato totalmente caricato nel portachiavi"
},
"importUsingSeed": {
"message": "Importa account con frase seed"
},
"info": {
"message": "Informazioni"
},
"infoHelp": {
"message": "Informazioni & Aiuto"
},
"initialTransactionConfirmed": {
"message": "La transazione iniziale è stata confermata dalla rete. Clicca OK per tornare indietro."
},
"insufficientFunds": {
"message": "Fondi non sufficienti."
},
@ -390,6 +552,9 @@
"invalidRPC": {
"message": "URI RPC invalido"
},
"invalidSeedPhrase": {
"message": "Frase seed non valida"
},
"jsonFail": {
"message": "Qualcosa è andato storto. Assicurati che il file JSON sia formattato correttamente."
},
@ -397,12 +562,24 @@
"message": "File JSON",
"description": "formato per importare un account"
},
"keepTrackTokens": {
"message": "Tieni traccia dei tokens che hai acquistato con il tuo account MetaMask."
},
"kovan": {
"message": "Rete di test Kovan"
},
"knowledgeDataBase": {
"message": "Visita la nostra Knowledge Base"
},
"max": {
"message": "Massimo"
},
"learnMore": {
"message": "Scopri di più"
},
"ledgerAccountRestriction": {
"message": "E' necessario utilizzare l'ultimo account prima di poterne aggiungere uno nuovo."
},
"lessThanMax": {
"message": "deve essere minore o uguale a $1.",
"description": "aiuto per inserire un input esadecimale come decimale"
@ -410,6 +587,9 @@
"likeToAddTokens": {
"message": "Vorresti aggiungere questi token?"
},
"links": {
"message": "Collegamenti"
},
"limit": {
"message": "Limite"
},
@ -437,17 +617,26 @@
"mainnet": {
"message": "Rete Ethereum Principale"
},
"menu": {
"message": "Menu"
},
"message": {
"message": "Messaggio"
},
"metamaskDescription": {
"message": "MetaMask è una cassaforte sicura per identità su Ethereum."
},
"metamaskSeedWords": {
"message": "Parole Seed di MetaMask"
},
"metamaskVersion": {
"message": "versione di MetaMask"
},
"min": {
"message": "Minimo"
},
"myAccounts": {
"message": "Account Miei"
"message": "Miei Account"
},
"mustSelectOne": {
"message": "Devi selezionare almeno un token."
@ -469,6 +658,9 @@
"networks": {
"message": "Reti"
},
"nevermind": {
"message": "Non importa"
},
"newAccount": {
"message": "Nuovo Account"
},
@ -482,6 +674,9 @@
"newPassword": {
"message": "Nuova Password (minimo 8 caratteri)"
},
"newPassword8Chars": {
"message": "Nuova Password (minimo 8 caratteri)"
},
"newRecipient": {
"message": "Nuovo Destinatario"
},
@ -489,7 +684,7 @@
"message": "Nuovo URL RPC"
},
"next": {
"message": "Prossimo"
"message": "Avanti"
},
"noAddressForName": {
"message": "Nessun indirizzo è stato impostato per questo nome."
@ -497,32 +692,75 @@
"noDeposits": {
"message": "Nessun deposito ricevuto"
},
"noConversionRateAvailable": {
"message": "Tasso di Conversione non Disponibile"
},
"noTransactionHistory": {
"message": "Nessuna cronologia delle transazioni."
},
"noTransactions": {
"message": "Nessuna Transazione"
},
"notFound": {
"message": "Non Trovata"
},
"notStarted": {
"message": "Non Iniziato"
},
"noWebcamFoundTitle": {
"message": "Webcam non trovata"
},
"noWebcamFound": {
"message": "La webcam del tuo computer non è stata trovata. Per favore riprovaci."
},
"oldUI": {
"message": "Vecchia interfaccia"
},
"oldUIMessage": {
"message": "Sei ritornato alla vecchia interfaccia. Puoi ritornare alla nuova interfaccia tramite l'opzione nel menu a discesa in alto a destra."
},
"onlySendToEtherAddress": {
"message": "Invia ETH solamente ad un account Ethereum."
},
"onlySendTokensToAccountAddress": {
"message": "Invia solamente $1 ad un account Ethereum.",
"description": "mostra il simbolo del token"
},
"openInTab": {
"message": "Apri in una scheda"
},
"or": {
"message": "o",
"description": "scelta tra creare o importare un nuovo account"
},
"orderOneHere": {
"message": "Compra un Trezor o un Ledger e tieni i tuoi soldi al sicuro"
},
"origin": {
"message": "Origine"
},
"outgoing": {
"message": "In Uscita"
},
"parameters": {
"message": "Parametri"
},
"password": {
"message": "Password"
},
"passwordCorrect": {
"message": "Assicurati che la password sia corretta."
},
"passwordsDontMatch": {
"message": "Le Password Non Corrispondonos"
},
"passwordMismatch": {
"message": "le password non corrispondono",
"description": "nella creazione della password, le due password all'interno dei campi non corrispondono"
},
"passwordNotLongEnough": {
"message": "Password non abbastanza lunga"
},
"passwordShort": {
"message": "password non sufficientemente lunga",
"description": "nella creazione della password, la password non è lunga abbastanza"
@ -534,12 +772,21 @@
"pasteSeed": {
"message": "Incolla la tua frase seed qui!"
},
"pending": {
"message": "in corso"
},
"personalAddressDetected": {
"message": "Rilevato indirizzo personale. Inserisci l'indirizzo del contratto del token."
},
"pleaseReviewTransaction": {
"message": "Ricontrolla la tua transazione."
},
"popularTokens": {
"message": "Tokens Popolari"
},
"prev": {
"message": "Precedente"
},
"privacyMsg": {
"message": "Politica sulla Privacy"
},
@ -556,6 +803,9 @@
"qrCode": {
"message": "Mostra Codice QR"
},
"queue": {
"message": "Coda"
},
"readdToken": {
"message": "Puoi aggiungere nuovamente questo token in futuro andando in “Aggiungi token” nel menu delle opzioni del tuo account."
},
@ -574,36 +824,87 @@
"refundAddress": {
"message": "Indirizzo di Rimborso"
},
"reject": {
"message": "Reject"
},
"rejectAll": {
"message": "Reject All"
},
"rejectTxsN": {
"message": "Reject $1 transactions"
},
"rejectTxsDescription": {
"message": "You are about to batch reject $1 transactions."
},
"rejected": {
"message": "Respinta"
},
"reset": {
"message": "Reset"
},
"resetAccount": {
"message": "Resetta Account"
},
"resetAccountDescription": {
"message": "Resettare il tuo account cancellerà lo storico delle transazioni."
},
"restoreFromSeed": {
"message": "Ripristina da una frase seed"
},
"restoreVault": {
"message": "Ripristina Cassaforte"
},
"restoreAccountWithSeed": {
"message": "Ripristina Account con la Frase Seed"
},
"required": {
"message": "Richiesto"
},
"retryWithMoreGas": {
"message": "Riprova con un prezzo del Gas maggiore qui"
},
"restore": {
"message": "Ripristina"
},
"revealSeedWords": {
"message": "Rivela Frase Seed"
},
"revealSeedWordsTitle": {
"message": "Frase Seed"
},
"revealSeedWordsDescription": {
"message": "Se cambierai browser o computer, ti servirà questa frase seed per accedere ai tuoi account. Salvala in un posto sicuro e segreto."
},
"revealSeedWordsWarningTitle": {
"message": "NON CONDIVIDERE questa frase con nessuno!"
},
"revealSeedWordsWarning": {
"message": "Non ripristinare la tua frase seed in pubblico!. Queste parole possono essere usate per rubare il tuo account."
},
"revert": {
"message": "Annulla"
},
"remove": {
"message": "rimuovi"
},
"removeAccount": {
"message": "Rimuovi account"
},
"removeAccountDescription": {
"message": "Questo account sarà rimosso dal tuo portafoglio. Per favore assicurati che hai la frase seed originale o la chiave privata per questo account importato prima di continuare. Puoi nuovamente importare o creare un account dal menù a tendina. "
},
"readyToConnect": {
"message": "Pronto a Connetterti?"
},
"rinkeby": {
"message": "Rete di test Rinkeby"
},
"ropsten": {
"message": "Rete di test Ropsten"
},
"rpc": {
"message": "RPC Personalizzata"
},
"sampleAccountName": {
"message": "Es: Il mio nuovo account",
"description": "Aiuta l'utente a capire il concetto di aggiungere un nome leggibile al loro account"
@ -611,6 +912,9 @@
"save": {
"message": "Salva"
},
"saveAsCsvFile": {
"message": "Salva Come File CSV"
},
"saveAsFile": {
"message": "Salva come File",
"description": "Processo per esportare un account"
@ -618,9 +922,18 @@
"saveSeedAsFile": {
"message": "Salva la Frase Seed come File"
},
"scanInstructions": {
"message": "Posizione il codice QR davanti alla fotocamera"
},
"scanQrCode": {
"message": "Scansiona Codice QR"
},
"search": {
"message": "Cerca"
},
"searchResults": {
"message": "Risultati Ricerca"
},
"secretPhrase": {
"message": "Inserisci la tua frase segreta di dodici parole per ripristinare la cassaforte."
},
@ -633,6 +946,9 @@
"selectCurrency": {
"message": "Seleziona Moneta"
},
"selectLocale": {
"message": "Selezione Lingua"
},
"selectService": {
"message": "Seleziona Servizio"
},
@ -648,6 +964,33 @@
"sendTokens": {
"message": "Invia Tokens"
},
"sentEther": {
"message": "ether inviati"
},
"sentTokens": {
"message": "tokens inviati"
},
"separateEachWord": {
"message": "Separa ogni parola con un solo spazio"
},
"searchTokens": {
"message": "Cerca Tokens"
},
"selectAnAddress": {
"message": "Seleziona un Indirizzo"
},
"selectAnAccount": {
"message": "Seleziona un Account"
},
"selectAnAccountHelp": {
"message": "Selezione l'account da visualizzare in MetaMask"
},
"selectHdPath": {
"message": "Seleziona Percorso HD"
},
"selectPathHelp": {
"message": "Se non vedi il tuo account Ledger esistente di seguito, prova a cambiare il percorso in \"Legacy (MEW / MyCrypto)\""
},
"sendTokensAnywhere": {
"message": "Invia Tokens a chiunque abbia un account Ethereum"
},
@ -663,9 +1006,21 @@
"showQRCode": {
"message": "Mostra Codie QR"
},
"showHexData": {
"message": "Mostra Dati Hex"
},
"showHexDataDescription": {
"message": "Seleziona per mostrare il campo dei dati hex nella schermata di invio"
},
"sign": {
"message": "Firma"
},
"signatureRequest": {
"message": "Firma Richiesta"
},
"signed": {
"message": "Firmata"
},
"signMessage": {
"message": "Firma Messaggio"
},
@ -681,6 +1036,15 @@
"spaceBetween": {
"message": "ci può essere solo uno spazio tra le parole"
},
"speedUp": {
"message": "velocizza"
},
"speedUpTitle": {
"message": "Velocizza Transazione"
},
"speedUpSubtitle": {
"message": "Aumenta il prezzo del gas per tentare di sovrascrivere e velocizzare la transazione"
},
"status": {
"message": "Stato"
},
@ -690,9 +1054,33 @@
"stateLogsDescription": {
"message": "I log di stato contengono i tuoi indirizzi pubblici e le transazioni effettuate."
},
"stateLogError": {
"message": "Errore nel recupero dei log di stato."
},
"step1HardwareWallet": {
"message": "1. Connetti Portafoglio Hardware"
},
"step1HardwareWalletMsg": {
"message": "Connetti il tuo portafoglio hardware al tuo computer."
},
"step2HardwareWallet": {
"message": "2. Seleziona un Account"
},
"step2HardwareWalletMsg": {
"message": "Selezione l'account che vuoi vedere. Puoi selezionarne solo uno alla volta."
},
"step3HardwareWallet": {
"message": "3. Inizia a usare dApps e molto altro ancora!"
},
"step3HardwareWalletMsg": {
"message": "Usa il tuo account hardware come utilizzeresti qualsiasi account Ethereum. Accedi alle dApps, invia Eth, compra e conserva token ERC20 e token non fungibili come CryptoKitties"
},
"submit": {
"message": "Invia"
},
"submitted": {
"message": "Inviata"
},
"supportCenter": {
"message": "Visita il nostro Centro di Supporto"
},
@ -715,6 +1103,9 @@
"message": "$1 a ETH via ShapeShift",
"description": "il sistema riempirà il tipo di deposito all'inizio del messaggio"
},
"token": {
"message": "Token"
},
"tokenAddress": {
"message": "Indirizzo Token"
},
@ -736,22 +1127,61 @@
"total": {
"message": "Totale"
},
"transaction": {
"message": "transazione"
},
"transactionConfirmed": {
"message": "Transazione confermata il $2."
},
"transactionCreated": {
"message": "Transazione di valore $1 creata il $2."
},
"transactionWithNonce": {
"message": "Transazione $1"
},
"transactionDropped": {
"message": "Transazione abbandonata il $2."
},
"transactionSubmitted": {
"message": "Transazione inviata il $2."
},
"transactionUpdated": {
"message": "Transazione aggiornata il $2."
},
"transactionUpdatedGas": {
"message": "Transazione aggiornata con un prezzo del gas di $1 il $2."
},
"transactions": {
"message": "transazioni"
},
"transactionError": {
"message": "Errore Transazione. Eccceziona generata nel codice del contratto."
},
"transactionMemo": {
"message": "Promemoria Transazione (opzionale)"
},
"transactionNumber": {
"message": "Numero Transazione"
},
"transfer": {
"message": "Trasferisci"
},
"transferFrom": {
"message": "Transfer From"
},
"transfers": {
"message": "Trasferimenti"
},
"trezorHardwareWallet": {
"message": "TREZOR Portafoglio Hardware"
},
"troubleTokenBalances": {
"message": "Abbiamo avuto un problema a caricare il bilancio dei tuoi token. Puoi vederlo ",
"description": "Seguito da un link (qui) per vedere il bilancio dei token"
},
"tryAgain": {
"message": "Prova di nuovo"
},
"twelveWords": {
"message": "Queste 12 parole sono l'unico modo per ripristinare i tuoi account MetaMask. \nSalvale in un posto sicuro e segreto."
},
@ -764,18 +1194,45 @@
"uiWelcomeMessage": {
"message": "Stai utilizzanto la nuova interfaccia di MetaMask. Guarda in giro, prova nuove funzionalità come inviare token, e facci sapere se hai dei problemi."
},
"unapproved": {
"message": "Non approvata"
},
"unavailable": {
"message": "Non Disponibile"
},
"units": {
"message": "unità"
},
"unknown": {
"message": "Sconosciuto"
},
"unknownFunction": {
"message": "Funzione Sconosciuta"
},
"unknownNetwork": {
"message": "Rete Privata Sconosciuta"
},
"unknownNetworkId": {
"message": "ID rete sconosciuto"
},
"unknownQrCode": {
"message": "Errore: Non siamo riusciti a identificare il codice QR"
},
"unknownCameraErrorTitle": {
"message": "Ooops! Qualcosa è andato storto...."
},
"unknownCameraError": {
"message": "C'è stato un errore nel tentativo di accedere alla fotocamera. Per favore prova di nuovo..."
},
"unlock": {
"message": "Sblocca"
},
"unlockMessage": {
"message": "Il web decentralizzato ti attende"
},
"updatedWithDate": {
"message": "Aggiornata $1"
},
"uriErrorMsg": {
"message": "Gli URI richiedono un prefisso HTTP/HTTPS."
},
@ -798,22 +1255,40 @@
"viewAccount": {
"message": "Vedi Account"
},
"viewOnEtherscan": {
"message": "Vedi su Etherscan"
},
"visitWebSite": {
"message": "Visita il nostro sito web"
},
"walletSeed": {
"message": "Seed del Portafoglio"
},
"warning": {
"message": "Attenzione"
},
"welcomeBack": {
"message": "Bentornato!"
},
"welcomeBeta": {
"message": "Benvenuto nella Beta di MetaMask"
},
"whatsThis": {
"message": "Cos'è questo?"
},
"yesLetsTry": {
"message": "Si, proviamo"
},
"youNeedToAllowCameraAccess": {
"message": "Devi consentire l'accesso alla fotocamera per usare questa funzionalità."
},
"yourSigRequested": {
"message": "E' richiesta la tua firma"
},
"youSign": {
"message": "Ti stai connettendo"
},
"yourPrivateSeedPhrase": {
"message": "La tua frase seed privata"
}
}

@ -14,9 +14,15 @@
"accountName": {
"message": "계정 이름"
},
"accountOptions": {
"message": "계정 옵션"
},
"accountSelectionRequired": {
"message": "계정을 선택하셔야 합니다!"
},
"activityLog": {
"message": "활동 로그"
},
"address": {
"message": "주소"
},
@ -29,6 +35,9 @@
"addTokens": {
"message": "토큰 추가"
},
"addSuggestedTokens": {
"message": "제안된 토큰 추가"
},
"addAcquiredTokens": {
"message": "메타마스크를 통해 획득한 토큰 추가"
},
@ -55,6 +64,9 @@
"attemptingConnect": {
"message": "블록체인에 접속을 시도하는 중입니다."
},
"attemptToCancel": {
"message": "취소 하시겠습니까?"
},
"attributions": {
"message": "속성"
},
@ -110,6 +122,15 @@
"cancel": {
"message": "취소"
},
"cancelAttempt": {
"message": "취소 시도"
},
"cancellationGasFee": {
"message": "취소 가스 수수료"
},
"cancelN": {
"message": "모든 $1 트랜잭션 취소"
},
"classicInterface": {
"message": "예전 인터페이스"
},
@ -220,7 +241,10 @@
"description": "거래 유형 (암호화폐)"
},
"currentConversion": {
"message": "선택된 단위"
"message": "현재 통화"
},
"currentLanguage": {
"message": "현재 언어"
},
"currentNetwork": {
"message": "현재 네트워크"
@ -340,6 +364,9 @@
"exchangeRate": {
"message": "환율"
},
"expandView": {
"message": "큰 화면으로 보기"
},
"exportPrivateKey": {
"message": "개인키 내보내기"
},
@ -457,6 +484,9 @@
"hideTokenPrompt": {
"message": "토큰 숨기기?"
},
"history": {
"message": "히스토리"
},
"howToDeposit": {
"message": "어떤 방법으로 이더를 입금하시겠습니까?"
},
@ -486,6 +516,9 @@
"importUsingSeed": {
"message": "계정 시드 구문으로 가져오기"
},
"info": {
"message": "정보"
},
"infoHelp": {
"message": "정보 및 도움말"
},
@ -539,7 +572,7 @@
"message": "최대"
},
"learnMore": {
"message": "더 배우기."
"message": "더 알아보기."
},
"ledgerAccountRestriction": {
"message": "새 계정을 추가하려면 최소 마지막 계정을 사용해야 합니다."
@ -590,6 +623,9 @@
"metamaskDescription": {
"message": "메타마스크는 이더리움을 위한 안전한 신분 저장소입니다."
},
"metamaskVersion": {
"message": "메타마스크 버전"
},
"metamaskSeedWords": {
"message": "메타마스크 시드 단어"
},
@ -727,6 +763,9 @@
"pasteSeed": {
"message": "시드 구문을 이곳에 붙여넣어 주세요!"
},
"pending": {
"message": "펜딩 중"
},
"personalAddressDetected": {
"message": "개인 주소가 탐지됨. 토큰 컨트랙트 주소를 입력하세요."
},
@ -755,6 +794,9 @@
"qrCode": {
"message": "QR 코드 보기"
},
"queue": {
"message": "큐"
},
"readdToken": {
"message": "옵션 메뉴에서 “토큰 추가”를 눌러서 추후에 다시 이 토큰을 추가하실 수 있습니다."
},
@ -773,6 +815,18 @@
"refundAddress": {
"message": "환불받을 주소"
},
"reject": {
"message": "거부"
},
"rejectAll": {
"message": "모두 거부"
},
"rejectTxsN": {
"message": "$1 트랜잭션 거부"
},
"rejectTxsDescription": {
"message": "$1 트랜잭션을 거부합니다."
},
"rejected": {
"message": "거부됨"
},
@ -892,6 +946,9 @@
"selectCurrency": {
"message": "통화 선택"
},
"selectLocale": {
"message": "언어 선택"
},
"selectService": {
"message": "서비스 선택"
},
@ -907,6 +964,12 @@
"sendTokens": {
"message": "토큰 전송"
},
"sentEther": {
"message": "전송된 이더"
},
"sentTokens": {
"message": "전송된 토큰"
},
"separateEachWord": {
"message": "각 단어는 공백 한칸으로 분리합니다"
},
@ -934,9 +997,6 @@
"settings": {
"message": "설정"
},
"info": {
"message": "정보"
},
"shapeshiftBuy": {
"message": "Shapeshift를 통해서 구매하기"
},
@ -946,12 +1006,21 @@
"showQRCode": {
"message": "QR 코드 보기"
},
"showHexData": {
"message": "Hex 데이터 보기"
},
"showHexDataDescription": {
"message": "선택하면 전송화면의 hex 데이터 필드 값을 보여줍니다."
},
"sign": {
"message": "서명"
},
"signed": {
"message": "서명됨"
},
"signatureRequest": {
"message": "서명 요청"
},
"signMessage": {
"message": "메시지 서명"
},
@ -1049,6 +1118,9 @@
"total": {
"message": "합계"
},
"transaction": {
"message": "트랜잭션"
},
"transactions": {
"message": "트랜잭션"
},
@ -1095,6 +1167,9 @@
"unavailable": {
"message": "이용할 수 없음"
},
"units": {
"message": "단위"
},
"unknown": {
"message": "알 수 없음"
},
@ -1165,11 +1240,14 @@
"whatsThis": {
"message": "이것은 무엇인가요?"
},
"yesLetsTry": {
"message": "네, 시도해보겠습니다."
},
"yourSigRequested": {
"message": "서명을 요청 중입니다."
},
"youSign": {
"message": "서명 중입니다"
"message": "서명니다"
},
"yourPrivateSeedPhrase": {
"message": "개인 시드 구문"

File diff suppressed because it is too large Load Diff

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 40 40" style="enable-background:new 0 0 40 40;" xml:space="preserve">
<style type="text/css">
.st0{fill:#38393A;}
</style>
<title>deposit-eth</title>
<desc>Created with Sketch.</desc>
<g id="deposit-eth" transform="translate(0.000000, 14.000000)">
<path id="Shape" class="st0" d="M19.9,16L7.5,8.7L19.9,26L32.3,8.7L19.9,16L19.9,16z M20.1-14L7.7,6.4l12.4,7.3l12.4-7.2L20.1-14z"
/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 670 B

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

@ -2,6 +2,9 @@
* @file The entry point for the web extension singleton process.
*/
// this needs to run before anything else
require('./lib/setupFetchDebugging')()
const urlUtil = require('url')
const endOfStream = require('end-of-stream')
const pump = require('pump')

@ -135,17 +135,22 @@ function doctypeCheck () {
}
/**
* Checks the current document extension
* Returns whether or not the extension (suffix) of the current document is prohibited
*
* @returns {boolean} {@code true} if the current extension is not prohibited
* This checks {@code window.location.pathname} against a set of file extensions
* that should not have web3 injected into them. This check is indifferent of query parameters
* in the location.
*
* @returns {boolean} whether or not the extension of the current document is prohibited
*/
function suffixCheck () {
var prohibitedTypes = ['xml', 'pdf']
var currentUrl = window.location.href
var currentRegex
const prohibitedTypes = [
/\.xml$/,
/\.pdf$/,
]
const currentUrl = window.location.pathname
for (let i = 0; i < prohibitedTypes.length; i++) {
currentRegex = new RegExp(`\\.${prohibitedTypes[i]}$`)
if (currentRegex.test(currentUrl)) {
if (prohibitedTypes[i].test(currentUrl)) {
return false
}
}

@ -38,6 +38,9 @@ class PreferencesController {
lostIdentities: {},
seedWords: null,
forgottenPassword: false,
preferences: {
useETHAsPrimaryCurrency: true,
},
}, opts.initState)
this.diagnostics = opts.diagnostics
@ -463,6 +466,33 @@ class PreferencesController {
getFeatureFlags () {
return this.store.getState().featureFlags
}
/**
* Updates the `preferences` property, which is an object. These are user-controlled features
* found in the settings page.
* @param {string} preference The preference to enable or disable.
* @param {boolean} value Indicates whether or not the preference should be enabled or disabled.
* @returns {Promise<object>} Promises a new object; the updated preferences object.
*/
setPreference (preference, value) {
const currentPreferences = this.getPreferences()
const updatedPreferences = {
...currentPreferences,
[preference]: value,
}
this.store.updateState({ preferences: updatedPreferences })
return Promise.resolve(updatedPreferences)
}
/**
* A getter for the `preferences` property
* @returns {object} A key-boolean map of user-selected preferences.
*/
getPreferences () {
return this.store.getState().preferences
}
//
// PRIVATE METHODS
//

@ -366,7 +366,40 @@ class TransactionController extends EventEmitter {
this.txStateManager.setTxStatusSubmitted(txId)
}
confirmTransaction (txId) {
/**
* Sets the status of the transaction to confirmed and sets the status of nonce duplicates as
* dropped if the txParams have data it will fetch the txReceipt
* @param {number} txId - The tx's ID
* @returns {Promise<void>}
*/
async confirmTransaction (txId) {
// get the txReceipt before marking the transaction confirmed
// to ensure the receipt is gotten before the ui revives the tx
const txMeta = this.txStateManager.getTx(txId)
if (!txMeta) {
return
}
try {
const txReceipt = await this.query.getTransactionReceipt(txMeta.hash)
// It seems that sometimes the numerical values being returned from
// this.query.getTransactionReceipt are BN instances and not strings.
const gasUsed = typeof txReceipt.gasUsed !== 'string'
? txReceipt.gasUsed.toString(16)
: txReceipt.gasUsed
txMeta.txReceipt = {
...txReceipt,
gasUsed,
}
this.txStateManager.updateTx(txMeta, 'transactions#confirmTransaction - add txReceipt')
} catch (err) {
log.error(err)
}
this.txStateManager.setTxStatusConfirmed(txId)
this._markNonceDuplicatesDropped(txId)
}

@ -400,6 +400,11 @@ class TransactionStateManager extends EventEmitter {
*/
_setTxStatus (txId, status) {
const txMeta = this.getTx(txId)
if (!txMeta) {
return
}
txMeta.status = status
setTimeout(() => {
try {

@ -27,6 +27,8 @@ var metamaskStream = new LocalMessageDuplexStream({
// compose the inpage provider
var inpageProvider = new MetamaskInpageProvider(metamaskStream)
// set a high max listener count to avoid unnecesary warnings
inpageProvider.setMaxListeners(100)
// Augment the provider with its enable method
inpageProvider.enable = function (options = {}) {

@ -0,0 +1,34 @@
module.exports = setupFetchDebugging
//
// This is a utility to help resolve cases where `window.fetch` throws a
// `TypeError: Failed to Fetch` without any stack or context for the request
// https://github.com/getsentry/sentry-javascript/pull/1293
//
function setupFetchDebugging() {
if (!global.fetch) return
const originalFetch = global.fetch
global.fetch = wrappedFetch
async function wrappedFetch(...args) {
const initialStack = getCurrentStack()
try {
return await originalFetch.call(window, ...args)
} catch (err) {
console.warn('FetchDebugger - fetch encountered an Error', err)
console.warn('FetchDebugger - overriding stack to point of original call')
err.stack = initialStack
throw err
}
}
}
function getCurrentStack() {
try {
throw new Error('Fake error for generating stack trace')
} catch (err) {
return err.stack
}
}

@ -129,6 +129,7 @@ module.exports = class MetamaskController extends EventEmitter {
provider: this.provider,
blockTracker: this.blockTracker,
})
// start and stop polling for balances based on activeControllerConnections
this.on('controllerConnectionChanged', (activeControllerConnections) => {
if (activeControllerConnections > 0) {
@ -138,6 +139,11 @@ module.exports = class MetamaskController extends EventEmitter {
}
})
// ensure accountTracker updates balances after network change
this.networkController.on('networkDidChange', () => {
this.accountTracker._updateAccounts()
})
// key mgmt
const additionalKeyrings = [TrezorKeyring, LedgerBridgeKeyring]
this.keyringController = new KeyringController({
@ -387,6 +393,7 @@ module.exports = class MetamaskController extends EventEmitter {
setCurrentAccountTab: nodeify(preferencesController.setCurrentAccountTab, preferencesController),
setAccountLabel: nodeify(preferencesController.setAccountLabel, preferencesController),
setFeatureFlag: nodeify(preferencesController.setFeatureFlag, preferencesController),
setPreference: nodeify(preferencesController.setPreference, preferencesController),
// BlacklistController
whitelistPhishingDomain: this.whitelistPhishingDomain.bind(this),

@ -107,7 +107,10 @@
"maxModeOn": false,
"editingTransactionId": null
},
"currentLocale": "en"
"currentLocale": "en",
"preferences": {
"useETHAsPrimaryCurrency": true
}
},
"appState": {
"menuOpen": false,

@ -150,7 +150,10 @@
"maxModeOn": false,
"editingTransactionId": null
},
"currentLocale": "en"
"currentLocale": "en",
"preferences": {
"useETHAsPrimaryCurrency": true
}
},
"appState": {
"menuOpen": false,

@ -108,7 +108,10 @@
"maxModeOn": false,
"editingTransactionId": null
},
"currentLocale": "en"
"currentLocale": "en",
"preferences": {
"useETHAsPrimaryCurrency": true
}
},
"appState": {
"menuOpen": false,

@ -37,7 +37,10 @@
"shapeShiftTxList": [],
"lostAccounts": [],
"tokens": [],
"currentLocale": "en"
"currentLocale": "en",
"preferences": {
"useETHAsPrimaryCurrency": true
}
},
"appState": {
"menuOpen": false,

@ -109,7 +109,10 @@
"maxModeOn": false,
"editingTransactionId": null
},
"currentLocale": "en"
"currentLocale": "en",
"preferences": {
"useETHAsPrimaryCurrency": true
}
},
"appState": {
"menuOpen": false,

@ -102,7 +102,10 @@
"shapeShiftTxList": [{"depositAddress":"34vJ3AfmNcLiziA4VFgEVcQTwxVLD1qkke","depositType":"BTC","key":"shapeshift","response":{"status":"no_deposits","address":"34vJ3AfmNcLiziA4VFgEVcQTwxVLD1qkke"},"time":1522377459106}],
"lostAccounts": [],
"send": {},
"currentLocale": "en"
"currentLocale": "en",
"preferences": {
"useETHAsPrimaryCurrency": true
}
},
"appState": {
"menuOpen": false,

202
package-lock.json generated

@ -9691,12 +9691,13 @@
"resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-1.4.2.tgz",
"integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=",
"requires": {
"ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
"ethereumjs-util": "^5.1.1"
},
"dependencies": {
"ethereumjs-abi": {
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"requires": {
"bn.js": "^4.10.0",
"ethereumjs-util": "^5.0.0"
@ -9771,12 +9772,13 @@
"resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-1.4.2.tgz",
"integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=",
"requires": {
"ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
"ethereumjs-util": "^5.1.1"
},
"dependencies": {
"ethereumjs-abi": {
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"requires": {
"bn.js": "^4.10.0",
"ethereumjs-util": "^5.0.0"
@ -9897,9 +9899,9 @@
}
},
"eth-json-rpc-middleware": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/eth-json-rpc-middleware/-/eth-json-rpc-middleware-3.1.1.tgz",
"integrity": "sha512-5mRpjmszVQbKaUk3kiKkP9+hyyD3ZIg3mJ+jiydZ46cfNbrFVzfTDvZKnYLPrQPEi4+CaYqF+7XnIHIGztd9JQ==",
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/eth-json-rpc-middleware/-/eth-json-rpc-middleware-3.1.3.tgz",
"integrity": "sha512-glp/mCefhsqrgVOTTuYlHYiTL+9mMPfaZsuQv4vnRg3kqNigblS1nqARaMeVW9WOM8ssh9TqIFpuUr7JDgNmKQ==",
"dev": true,
"requires": {
"async": "^2.5.0",
@ -10002,12 +10004,13 @@
"resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-1.4.2.tgz",
"integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=",
"requires": {
"ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
"ethereumjs-util": "^5.1.1"
},
"dependencies": {
"ethereumjs-abi": {
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"requires": {
"bn.js": "^4.10.0",
"ethereumjs-util": "^5.0.0"
@ -10068,12 +10071,13 @@
"resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-1.4.2.tgz",
"integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=",
"requires": {
"ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
"ethereumjs-util": "^5.1.1"
},
"dependencies": {
"ethereumjs-abi": {
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"requires": {
"bn.js": "^4.10.0",
"ethereumjs-util": "^5.0.0"
@ -10333,12 +10337,13 @@
"resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-1.4.2.tgz",
"integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=",
"requires": {
"ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
"ethereumjs-util": "^5.1.1"
},
"dependencies": {
"ethereumjs-abi": {
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"requires": {
"bn.js": "^4.10.0",
"ethereumjs-util": "^5.0.0"
@ -10371,21 +10376,21 @@
}
},
"eth-token-tracker": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/eth-token-tracker/-/eth-token-tracker-1.1.4.tgz",
"integrity": "sha1-Kf8kV9Zr+juO5JDoP/QP0M8s7EE=",
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/eth-token-tracker/-/eth-token-tracker-1.1.5.tgz",
"integrity": "sha512-dyNzEt62i5vpbAAHHj6kEVxSHg/WqCr7TBq1Sbs4y0PvsxcvfWLJpEYtJilndg36H7nJHGadgmHqGW5mYbcNfw==",
"requires": {
"deep-equal": "^1.0.1",
"eth-block-tracker": "^1.0.7",
"ethjs": "^0.2.7",
"ethjs-contract": "^0.1.9",
"ethjs-query": "^0.2.6",
"ethjs": "^0.3.6",
"ethjs-contract": "^0.2.1",
"ethjs-query": "^0.3.7",
"human-standard-token-abi": "^1.0.2"
},
"dependencies": {
"babelify": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/babelify/-/babelify-7.3.0.tgz",
"resolved": "http://registry.npmjs.org/babelify/-/babelify-7.3.0.tgz",
"integrity": "sha1-qlau3nBn/XvVSWZu4W3ChQh+iOU=",
"requires": {
"babel-core": "^6.0.14",
@ -10408,105 +10413,58 @@
"ethjs-util": "^0.1.3",
"pify": "^2.3.0",
"tape": "^4.6.3"
},
"dependencies": {
"pify": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw="
}
}
},
"ethjs": {
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/ethjs/-/ethjs-0.2.9.tgz",
"integrity": "sha1-yagNR7ydVg9Z53gEnSIlXlgfMSs=",
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/ethjs/-/ethjs-0.3.9.tgz",
"integrity": "sha512-gOQzA3tDUjoLpNONSOALJ/rUFtHi5tXl2mholHasF1cvXhoddqi06yU4OJFJu9AGd6n9v9ywzHlYeIKg1t1hdw==",
"requires": {
"bn.js": "4.11.6",
"ethjs-abi": "0.2.0",
"ethjs-contract": "0.1.9",
"ethjs-filter": "0.1.5",
"ethjs-abi": "0.2.1",
"ethjs-contract": "0.2.2",
"ethjs-filter": "0.1.8",
"ethjs-provider-http": "0.1.6",
"ethjs-query": "0.3.0",
"ethjs-query": "0.3.7",
"ethjs-unit": "0.1.6",
"ethjs-util": "0.1.3",
"js-sha3": "0.5.5",
"number-to-bn": "1.7.0"
},
"dependencies": {
"ethjs-format": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/ethjs-format/-/ethjs-format-0.2.3.tgz",
"integrity": "sha1-m9hnyu6CstvtmEYAuzAiDPPLWDA=",
"requires": {
"bn.js": "4.11.6",
"ethjs-schema": "^0.1.6",
"ethjs-util": "0.1.3",
"is-hex-prefixed": "1.0.0",
"number-to-bn": "1.7.0",
"strip-hex-prefix": "1.0.0"
}
},
"ethjs-query": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/ethjs-query/-/ethjs-query-0.3.0.tgz",
"integrity": "sha1-CAmNYQ+BvV+VSnpXq0mJ9+mBX8Q=",
"requires": {
"ethjs-format": "0.2.3",
"ethjs-rpc": "0.1.5"
}
},
"ethjs-schema": {
"version": "0.1.9",
"resolved": "https://registry.npmjs.org/ethjs-schema/-/ethjs-schema-0.1.9.tgz",
"integrity": "sha1-hYwqXacGrgSBK0zosetLSSHjMJI="
},
"ethjs-util": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.3.tgz",
"integrity": "sha1-39XqSkANxeQhqInK9H4IGtp4u1U=",
"requires": {
"is-hex-prefixed": "1.0.0",
"strip-hex-prefix": "1.0.0"
}
}
}
},
"ethjs-contract": {
"version": "0.1.9",
"resolved": "https://registry.npmjs.org/ethjs-contract/-/ethjs-contract-0.1.9.tgz",
"integrity": "sha1-HCdmiWpW1H7B1tZhgpxJzDilUgo=",
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/ethjs-contract/-/ethjs-contract-0.2.2.tgz",
"integrity": "sha512-xxPqEjsULQ/QNWuvX6Ako0PGs5RxALA8N/H3+boLvnaXDFZVGpD7H63H1gBCRTZyYqCldPpVlVHuw/rD45vazw==",
"requires": {
"ethjs-abi": "0.2.0",
"ethjs-filter": "0.1.5",
"ethjs-filter": "0.1.8",
"ethjs-util": "0.1.3",
"js-sha3": "0.5.5"
},
"dependencies": {
"ethjs-util": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.3.tgz",
"integrity": "sha1-39XqSkANxeQhqInK9H4IGtp4u1U=",
"ethjs-abi": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/ethjs-abi/-/ethjs-abi-0.2.0.tgz",
"integrity": "sha1-0+LCIQEVIPxJm3FoIDbBT8wvWyU=",
"requires": {
"is-hex-prefixed": "1.0.0",
"strip-hex-prefix": "1.0.0"
"bn.js": "4.11.6",
"js-sha3": "0.5.5",
"number-to-bn": "1.7.0"
}
}
}
},
"ethjs-format": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/ethjs-format/-/ethjs-format-0.2.2.tgz",
"integrity": "sha1-1zs6YFwuElcHn3B3/VRI6ZjOD80=",
"ethjs-query": {
"version": "0.3.7",
"resolved": "https://registry.npmjs.org/ethjs-query/-/ethjs-query-0.3.7.tgz",
"integrity": "sha512-TZnKUwfkWjy0SowFdPLtmsytCorHi0i4vvkQn7Jg8rZt33cRzKhuzOwKr/G3vdigCc+ePXOhUGMcJSAPlOG44A==",
"requires": {
"bn.js": "4.11.6",
"ethjs-schema": "0.1.5",
"ethjs-util": "0.1.3",
"is-hex-prefixed": "1.0.0",
"number-to-bn": "1.7.0",
"strip-hex-prefix": "1.0.0"
"ethjs-format": "0.2.7",
"ethjs-rpc": "0.2.0",
"promise-to-callback": "^1.0.0"
}
},
"dependencies": {
"ethjs-util": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.3.tgz",
@ -10518,19 +10476,39 @@
}
}
},
"ethjs-abi": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/ethjs-abi/-/ethjs-abi-0.2.1.tgz",
"integrity": "sha1-4KepOn6BFjqUR3utVu3lJKtt5TM=",
"requires": {
"bn.js": "4.11.6",
"js-sha3": "0.5.5",
"number-to-bn": "1.7.0"
}
},
"ethjs-filter": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/ethjs-filter/-/ethjs-filter-0.1.8.tgz",
"integrity": "sha512-qTDPskDL2UadHwjvM8A+WG9HwM4/FoSY3p3rMJORkHltYcAuiQZd2otzOYKcL5w2Q3sbAkW/E3yt/FPFL/AVXA=="
},
"ethjs-query": {
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/ethjs-query/-/ethjs-query-0.2.9.tgz",
"integrity": "sha1-om5rTzhpnpLzSyGE51x4lDKcQvE=",
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/ethjs-query/-/ethjs-query-0.3.8.tgz",
"integrity": "sha512-/J5JydqrOzU8O7VBOwZKUWXxHDGr46VqNjBCJgBVNNda+tv7Xc8Y2uJc6aMHHVbeN3YOQ7YRElgIc0q1CI02lQ==",
"requires": {
"ethjs-format": "0.2.2",
"ethjs-rpc": "0.1.5"
"babel-runtime": "^6.26.0",
"ethjs-format": "0.2.7",
"ethjs-rpc": "0.2.0",
"promise-to-callback": "^1.0.0"
}
},
"ethjs-schema": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/ethjs-schema/-/ethjs-schema-0.1.5.tgz",
"integrity": "sha1-WXQOOzl3vNu5sRvDBoIB6Kzquw0="
"ethjs-rpc": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/ethjs-rpc/-/ethjs-rpc-0.2.0.tgz",
"integrity": "sha512-RINulkNZTKnj4R/cjYYtYMnFFaBcVALzbtEJEONrrka8IeoarNB9Jbzn+2rT00Cv8y/CxAI+GgY1d0/i2iQeOg==",
"requires": {
"promise-to-callback": "^1.0.0"
}
},
"human-standard-token-abi": {
"version": "1.0.2",
@ -10541,6 +10519,11 @@
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.5.tgz",
"integrity": "sha1-uvDA6MVK1ZA0R9+Wreekobynmko="
},
"pify": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw="
}
}
},
@ -10561,12 +10544,13 @@
"resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-1.4.2.tgz",
"integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=",
"requires": {
"ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
"ethereumjs-util": "^5.1.1"
},
"dependencies": {
"ethereumjs-abi": {
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"requires": {
"bn.js": "^4.10.0",
"ethereumjs-util": "^5.0.0"
@ -10799,12 +10783,13 @@
"resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-1.4.2.tgz",
"integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=",
"requires": {
"ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
"ethereumjs-util": "^5.1.1"
},
"dependencies": {
"ethereumjs-abi": {
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"requires": {
"bn.js": "^4.10.0",
"ethereumjs-util": "^5.0.0"
@ -13752,12 +13737,14 @@
"integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=",
"dev": true,
"requires": {
"ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
"ethereumjs-util": "^5.1.1"
},
"dependencies": {
"ethereumjs-abi": {
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"dev": true,
"requires": {
"bn.js": "^4.10.0",
"ethereumjs-util": "^5.0.0"
@ -32800,6 +32787,7 @@
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
"integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
"dev": true,
"requires": {
"is-typedarray": "^1.0.0"
}
@ -33825,6 +33813,7 @@
"resolved": "https://registry.npmjs.org/web3/-/web3-0.20.3.tgz",
"integrity": "sha1-yqRDc9yIFayHZ73ba6cwc5ZMqos=",
"requires": {
"bignumber.js": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934",
"crypto-js": "^3.1.4",
"utf8": "^2.1.1",
"xhr2": "*",
@ -33833,7 +33822,7 @@
"dependencies": {
"bignumber.js": {
"version": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934",
"from": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934"
"from": "git+https://github.com/frozeman/bignumber.js-nolookahead.git"
}
}
},
@ -34231,7 +34220,8 @@
"dev": true,
"requires": {
"underscore": "1.8.3",
"web3-core-helpers": "1.0.0-beta.34"
"web3-core-helpers": "1.0.0-beta.34",
"websocket": "git://github.com/frozeman/WebSocket-Node.git#6c72925e3f8aaaea8dc8450f97627e85263999f2"
},
"dependencies": {
"underscore": {
@ -34241,8 +34231,9 @@
"dev": true
},
"websocket": {
"version": "git://github.com/frozeman/WebSocket-Node.git#7004c39c42ac98875ab61126e5b4a925430f592c",
"from": "git://github.com/frozeman/WebSocket-Node.git#7004c39c42ac98875ab61126e5b4a925430f592c",
"version": "git://github.com/frozeman/WebSocket-Node.git#6c72925e3f8aaaea8dc8450f97627e85263999f2",
"from": "git://github.com/frozeman/WebSocket-Node.git#browserifyCompatible",
"dev": true,
"requires": {
"debug": "^2.2.0",
"nan": "^2.3.3",
@ -34856,7 +34847,8 @@
"yaeti": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz",
"integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc="
"integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=",
"dev": true
},
"yallist": {
"version": "2.1.2",

@ -124,7 +124,7 @@
"eth-phishing-detect": "^1.1.4",
"eth-query": "^2.1.2",
"eth-sig-util": "^2.0.2",
"eth-token-tracker": "^1.1.4",
"eth-token-tracker": "^1.1.5",
"eth-trezor-keyring": "^0.1.0",
"ethereumjs-abi": "^0.6.4",
"ethereumjs-tx": "^1.3.0",
@ -261,7 +261,7 @@
"eslint-plugin-json": "^1.2.0",
"eslint-plugin-mocha": "^5.0.0",
"eslint-plugin-react": "^7.4.0",
"eth-json-rpc-middleware": "^3.1.1",
"eth-json-rpc-middleware": "^3.1.3",
"eth-keyring-controller": "^3.3.1",
"fetch-mock": "^6.5.2",
"file-loader": "^1.1.11",

@ -286,7 +286,7 @@ describe('Using MetaMask with an existing account', function () {
await delay(regularDelayMs)
const inputAddress = await findElement(driver, By.css('input[placeholder="Recipient Address"]'))
const inputAmount = await findElement(driver, By.css('.currency-display__input'))
const inputAmount = await findElement(driver, By.css('.unit-input__input'))
await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970')
await inputAmount.sendKeys('1')

@ -383,7 +383,7 @@ describe('MetaMask', function () {
await delay(regularDelayMs)
const inputAddress = await findElement(driver, By.css('input[placeholder="Recipient Address"]'))
const inputAmount = await findElement(driver, By.css('.currency-display__input'))
const inputAmount = await findElement(driver, By.css('.unit-input__input'))
await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970')
await inputAmount.sendKeys('1')
@ -662,7 +662,7 @@ describe('MetaMask', function () {
})
it('clicks on the Add Token button', async () => {
const addToken = await driver.findElement(By.css('.wallet-view__add-token-button'))
const addToken = await driver.findElement(By.xpath(`//div[contains(text(), 'Add Token')]`))
await addToken.click()
await delay(regularDelayMs)
})
@ -702,7 +702,7 @@ describe('MetaMask', function () {
await delay(regularDelayMs)
const inputAddress = await findElement(driver, By.css('input[placeholder="Recipient Address"]'))
const inputAmount = await findElement(driver, By.css('.currency-display__input'))
const inputAmount = await findElement(driver, By.css('.unit-input__input'))
await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970')
await inputAmount.sendKeys('50')
@ -834,8 +834,8 @@ describe('MetaMask', function () {
await save.click()
await driver.wait(until.stalenessOf(gasModal))
const gasFeeInputs = await findElements(driver, By.css('.confirm-detail-row__eth'))
assert.equal(await gasFeeInputs[0].getText(), '0.0006')
const gasFeeInputs = await findElements(driver, By.css('.confirm-detail-row__primary'))
assert.equal(await gasFeeInputs[0].getText(), '0.0006')
})
it('submits the transaction', async function () {
@ -957,8 +957,8 @@ describe('MetaMask', function () {
await save.click()
await driver.wait(until.stalenessOf(gasModal))
const gasFeeInputs = await findElements(driver, By.css('.confirm-detail-row__eth'))
assert.equal(await gasFeeInputs[0].getText(), '0.0006')
const gasFeeInputs = await findElements(driver, By.css('.confirm-detail-row__primary'))
assert.equal(await gasFeeInputs[0].getText(), '0.0006')
})
it('submits the transaction', async function () {
@ -1002,7 +1002,7 @@ describe('MetaMask', function () {
describe('Add existing token using search', () => {
it('clicks on the Add Token button', async () => {
const addToken = await findElement(driver, By.xpath(`//button[contains(text(), 'Add Token')]`))
const addToken = await findElement(driver, By.xpath(`//div[contains(text(), 'Add Token')]`))
await addToken.click()
await delay(regularDelayMs)
})

@ -1,140 +0,0 @@
const reactTriggerChange = require('react-trigger-change')
const {
timeout,
queryAsync,
findAsync,
} = require('../../lib/util')
QUnit.module('Add token flow')
QUnit.test('successful add token flow', (assert) => {
const done = assert.async()
runAddTokenFlowTest(assert)
.then(done)
.catch(err => {
assert.notOk(err, `Error was thrown: ${err.stack}`)
done()
})
})
async function runAddTokenFlowTest (assert, done) {
const selectState = await queryAsync($, 'select')
selectState.val('add token')
reactTriggerChange(selectState[0])
// Used to set values on TextField input component
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
window.HTMLInputElement.prototype, 'value'
).set
// Check that no tokens have been added
assert.ok($('.token-list-item').length === 0, 'no tokens added')
// Go to Add Token screen
let addTokenButton = await queryAsync($, 'button.btn-primary.wallet-view__add-token-button')
assert.ok(addTokenButton[0], 'add token button present')
addTokenButton[0].click()
// Verify Add Token screen
let addTokenWrapper = await queryAsync($, '.page-container')
assert.ok(addTokenWrapper[0], 'add token wrapper renders')
let addTokenTitle = await queryAsync($, '.page-container__title')
assert.equal(addTokenTitle[0].textContent, 'Add Tokens', 'add token title is correct')
// Cancel Add Token
const cancelAddTokenButton = await queryAsync($, 'button.btn-default.btn--large.page-container__footer-button')
assert.ok(cancelAddTokenButton[0], 'cancel add token button present')
cancelAddTokenButton.click()
assert.ok($('.wallet-view')[0], 'cancelled and returned to account detail wallet view')
// Return to Add Token Screen
addTokenButton = await queryAsync($, 'button.btn-primary.wallet-view__add-token-button')
assert.ok(addTokenButton[0], 'add token button present')
addTokenButton[0].click()
// Verify Add Token Screen
addTokenWrapper = await queryAsync($, '.page-container')
addTokenTitle = await queryAsync($, '.page-container__title')
assert.ok(addTokenWrapper[0], 'add token wrapper renders')
assert.equal(addTokenTitle[0].textContent, 'Add Tokens', 'add token title is correct')
// Search for token
const searchInput = (await findAsync(addTokenWrapper, '#search-tokens'))[0]
searchInput.focus()
await timeout(1000)
nativeInputValueSetter.call(searchInput, 'a')
searchInput.dispatchEvent(new Event('input', { bubbles: true}))
// Click token to add
const tokenWrapper = await queryAsync($, 'div.token-list__token')
assert.ok(tokenWrapper[0], 'token found')
const tokenImageProp = tokenWrapper.find('.token-list__token-icon').css('background-image')
const tokenImageUrl = tokenImageProp.slice(5, -2)
tokenWrapper[0].click()
// Click Next button
const nextButton = await queryAsync($, 'button.btn-primary.btn--large')
assert.equal(nextButton[0].textContent, 'Next', 'next button rendered')
nextButton[0].click()
// Confirm Add token
const confirmAddToken = await queryAsync($, '.confirm-add-token')
assert.ok(confirmAddToken[0], 'confirm add token rendered')
assert.ok($('button.btn-primary.btn--large')[0], 'confirm add token button found')
$('button.btn-primary.btn--large')[0].click()
// Verify added token image
let heroBalance = await queryAsync($, '.transaction-view-balance__balance-container')
assert.ok(heroBalance, 'rendered hero balance')
assert.ok(tokenImageUrl.indexOf(heroBalance.find('img').attr('src')) > -1, 'token added')
// Return to Add Token Screen
addTokenButton = await queryAsync($, 'button.btn-primary.wallet-view__add-token-button')
assert.ok(addTokenButton[0], 'add token button present')
addTokenButton[0].click()
addTokenWrapper = await queryAsync($, '.page-container')
const addTokenTabs = await queryAsync($, '.page-container__tab')
assert.equal(addTokenTabs.length, 2, 'expected number of tabs')
assert.equal(addTokenTabs[1].textContent, 'Custom Token', 'Custom Token tab present')
assert.ok(addTokenTabs[1], 'add custom token tab present')
addTokenTabs[1].click()
await timeout(1000)
// Input token contract address
const customInput = (await findAsync(addTokenWrapper, '#custom-address'))[0]
customInput.focus()
await timeout(1000)
nativeInputValueSetter.call(customInput, '0x177af043D3A1Aed7cc5f2397C70248Fc6cDC056c')
customInput.dispatchEvent(new Event('input', { bubbles: true}))
// Click Next button
// nextButton = await queryAsync($, 'button.btn-primary--lg')
// assert.equal(nextButton[0].textContent, 'Next', 'next button rendered')
// nextButton[0].click()
// // Verify symbol length error since contract address won't return symbol
const errorMessage = await queryAsync($, '#custom-symbol-helper-text')
assert.ok(errorMessage[0], 'error rendered')
$('button.btn-default.btn--large')[0].click()
// await timeout(100000)
// Confirm Add token
// assert.equal(
// $('.page-container__subtitle')[0].textContent,
// 'Would you like to add these tokens?',
// 'confirm add token rendered'
// )
// assert.ok($('button.btn-primary--lg')[0], 'confirm add token button found')
// $('button.btn-primary--lg')[0].click()
// Verify added token image
heroBalance = await queryAsync($, '.transaction-view-balance__balance-container')
assert.ok(heroBalance, 'rendered hero balance')
assert.ok(heroBalance.find('.identicon')[0], 'token added')
}

@ -40,7 +40,7 @@ async function customizeGas (assert, price, limit, ethFee, usdFee) {
const sendGasField = await queryAsync($, '.send-v2__gas-fee-display')
assert.equal(
(await findAsync(sendGasField, '.currency-display__input-wrapper > input')).val(),
(await findAsync(sendGasField, '.currency-display-component'))[0].textContent,
ethFee,
'send gas field should show customized gas total'
)
@ -97,9 +97,9 @@ async function runSendFlowTest (assert, done) {
assert.equal(sendToAccountAddress, '0x2f8D4a878cFA04A6E60D46362f5644DeAb66572D', 'send to dropdown selects the correct address')
const sendAmountField = await queryAsync($, '.send-v2__form-row:eq(2)')
sendAmountField.find('.currency-display')[0].click()
sendAmountField.find('.unit-input')[0].click()
const sendAmountFieldInput = await findAsync(sendAmountField, '.currency-display__input')
const sendAmountFieldInput = await findAsync(sendAmountField, '.unit-input__input')
sendAmountFieldInput.val('5.1')
reactTriggerChange(sendAmountField.find('input')[0])
@ -112,9 +112,9 @@ async function runSendFlowTest (assert, done) {
errorMessage = $('.send-v2__error')
assert.equal(errorMessage.length, 0, 'send should stop rendering amount error message after amount is corrected')
await customizeGas(assert, 0, 21000, '0', '$0.00 USD')
await customizeGas(assert, 1, 21000, '0.000021', '$0.03 USD')
await customizeGas(assert, 500, 60000, '0.03', '$36.03 USD')
await customizeGas(assert, 0, 21000, '0 ETH', '$0.00 USD')
await customizeGas(assert, 1, 21000, '0.000021 ETH', '$0.03 USD')
await customizeGas(assert, 500, 60000, '0.03 ETH', '$36.03 USD')
const sendButton = await queryAsync($, 'button.btn-primary.btn--large.page-container__footer-button')
assert.equal(sendButton[0].textContent, 'Next', 'next button rendered')
@ -130,11 +130,11 @@ async function runSendFlowTest (assert, done) {
const confirmToName = (await queryAsync($, '.sender-to-recipient__name')).last()
assert.equal(confirmToName[0].textContent, 'Send Account 3', 'confirm screen should show correct to name')
const confirmScreenRowFiats = await queryAsync($, '.confirm-detail-row__fiat')
const confirmScreenRowFiats = await queryAsync($, '.confirm-detail-row__secondary')
const confirmScreenGas = confirmScreenRowFiats[0]
assert.equal(confirmScreenGas.textContent, '$3.60', 'confirm screen should show correct gas')
const confirmScreenTotal = confirmScreenRowFiats[1]
assert.equal(confirmScreenTotal.textContent, '$2,405.36', 'confirm screen should show correct total')
assert.equal(confirmScreenTotal.textContent, '$2,405.37', 'confirm screen should show correct total')
const confirmScreenBackButton = await queryAsync($, '.confirm-page-container-header__back-button')
confirmScreenBackButton[0].click()
@ -150,9 +150,9 @@ async function runSendFlowTest (assert, done) {
sendToFieldInputInEdit.val('0xd85a4b6a394794842887b8284293d69163007bbb')
const sendAmountFieldInEdit = await queryAsync($, '.send-v2__form-row:eq(2)')
sendAmountFieldInEdit.find('.currency-display')[0].click()
sendAmountFieldInEdit.find('.unit-input')[0].click()
const sendAmountFieldInputInEdit = sendAmountFieldInEdit.find('.currency-display__input')
const sendAmountFieldInputInEdit = sendAmountFieldInEdit.find('.unit-input__input')
sendAmountFieldInputInEdit.val('1.0')
reactTriggerChange(sendAmountFieldInputInEdit[0])

@ -1,44 +0,0 @@
const assert = require('assert')
const h = require('react-hyperscript')
const { createMockStore } = require('redux-test-utils')
const { shallowWithStore } = require('../../lib/render-helpers')
const BalanceComponent = require('../../../ui/app/components/balance-component')
const mockState = {
metamask: {
accounts: { abc: {} },
network: 1,
selectedAddress: 'abc',
},
}
describe('BalanceComponent', function () {
let balanceComponent
let store
let component
beforeEach(function () {
store = createMockStore(mockState)
component = shallowWithStore(h(BalanceComponent), store)
balanceComponent = component.dive()
})
it('shows token balance and convert to fiat value based on conversion rate', function () {
const formattedBalance = '1.23 ETH'
const tokenBalance = balanceComponent.instance().getTokenBalance(formattedBalance, false)
const fiatDisplayNumber = balanceComponent.instance().getFiatDisplayNumber(formattedBalance, 2)
assert.equal('1.23 ETH', tokenBalance)
assert.equal(2.46, fiatDisplayNumber)
})
it('shows only the token balance when conversion rate is not available', function () {
const formattedBalance = '1.23 ETH'
const tokenBalance = balanceComponent.instance().getTokenBalance(formattedBalance, false)
const fiatDisplayNumber = balanceComponent.instance().getFiatDisplayNumber(formattedBalance, 0)
assert.equal('1.23 ETH', tokenBalance)
assert.equal('N/A', fiatDisplayNumber)
})
})

@ -305,6 +305,12 @@ var actions = {
updateFeatureFlags,
UPDATE_FEATURE_FLAGS: 'UPDATE_FEATURE_FLAGS',
// Preferences
setPreference,
updatePreferences,
UPDATE_PREFERENCES: 'UPDATE_PREFERENCES',
setUseETHAsPrimaryCurrencyPreference,
setMouseUserState,
SET_MOUSE_USER_STATE: 'SET_MOUSE_USER_STATE',
@ -2298,6 +2304,36 @@ function updateFeatureFlags (updatedFeatureFlags) {
}
}
function setPreference (preference, value) {
return dispatch => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
background.setPreference(preference, value, (err, updatedPreferences) => {
dispatch(actions.hideLoadingIndication())
if (err) {
dispatch(actions.displayWarning(err.message))
return reject(err)
}
dispatch(actions.updatePreferences(updatedPreferences))
resolve(updatedPreferences)
})
})
}
}
function updatePreferences (value) {
return {
type: actions.UPDATE_PREFERENCES,
value,
}
}
function setUseETHAsPrimaryCurrencyPreference (value) {
return setPreference('useETHAsPrimaryCurrency', value)
}
function setNetworkNonce (networkNonce) {
return {
type: actions.SET_NETWORK_NONCE,

@ -8,11 +8,11 @@ const h = require('react-hyperscript')
const actions = require('../../actions')
const { Menu, Item, Divider, CloseArea } = require('../dropdowns/components/menu')
const Identicon = require('../identicon')
const { formatBalance } = require('../../util')
const { ENVIRONMENT_TYPE_POPUP } = require('../../../../app/scripts/lib/enums')
const { getEnvironmentType } = require('../../../../app/scripts/lib/util')
const Tooltip = require('../tooltip')
import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'
import { PRIMARY } from '../../constants/common'
const {
SETTINGS_ROUTE,
@ -163,7 +163,6 @@ AccountMenu.prototype.renderAccounts = function () {
const isSelected = identity.address === selectedAddress
const balanceValue = accounts[address] ? accounts[address].balance : ''
const formattedBalance = balanceValue ? formatBalance(balanceValue, 6) : '...'
const simpleAddress = identity.address.substring(2).toLowerCase()
const keyring = keyrings.find((kr) => {
@ -189,7 +188,11 @@ AccountMenu.prototype.renderAccounts = function () {
h('div.account-menu__account-info', [
h('div.account-menu__name', identity.name || ''),
h('div.account-menu__balance', formattedBalance),
h(UserPreferencedCurrencyDisplay, {
className: 'account-menu__balance',
value: balanceValue,
type: PRIMARY,
}),
]),
this.renderKeyringType(keyring),

@ -0,0 +1,34 @@
import PropTypes from 'prop-types'
import React, {PureComponent} from 'react'
export default class AddTokenButton extends PureComponent {
static contextTypes = {
t: PropTypes.func.isRequired,
}
static defaultProps = {
onClick: () => {},
}
static propTypes = {
onClick: PropTypes.func,
}
render () {
const { t } = this.context
const { onClick } = this.props
return (
<div className="add-token-button">
<h1 className="add-token-button__help-header">{t('missingYourTokens')}</h1>
<p className="add-token-button__help-desc">{t('clickToAdd', [t('addToken')])}</p>
<div
className="add-token-button__button"
onClick={onClick}
>
{t('addToken')}
</div>
</div>
)
}
}

@ -0,0 +1 @@
export { default } from './add-token-button.component'

@ -0,0 +1,26 @@
.add-token-button {
display: flex;
flex-direction: column;
color: lighten($scorpion, 25%);
width: 185px;
margin: 36px auto;
text-align: center;
&__help-header {
font-weight: bold;
font-size: 1rem;
}
&__help-desc {
font-size: 0.75rem;
margin-top: 1rem;
}
&__button {
font-size: 0.75rem;
margin: 1rem;
text-transform: uppercase;
color: $curious-blue;
cursor: pointer;
}
}

@ -4,10 +4,11 @@ const h = require('react-hyperscript')
const inherits = require('util').inherits
const TokenBalance = require('./token-balance')
const Identicon = require('./identicon')
import CurrencyDisplay from './currency-display'
import UserPreferencedCurrencyDisplay from './user-preferenced-currency-display'
import { PRIMARY, SECONDARY } from '../constants/common'
const { getAssetImages, conversionRateSelector, getCurrentCurrency} = require('../selectors')
const { formatBalance, generateBalanceObject } = require('../util')
const { formatBalance } = require('../util')
module.exports = connect(mapStateToProps)(BalanceComponent)
@ -65,7 +66,7 @@ BalanceComponent.prototype.renderTokenBalance = function () {
BalanceComponent.prototype.renderBalance = function () {
const props = this.props
const { shorten, account } = props
const { account } = props
const balanceValue = account && account.balance
const needsParse = 'needsParse' in props ? props.needsParse : true
const formattedBalance = balanceValue ? formatBalance(balanceValue, 6, needsParse) : '...'
@ -80,25 +81,20 @@ BalanceComponent.prototype.renderBalance = function () {
}
return h('div.flex-column.balance-display', {}, [
h('div.token-amount', {
style: {},
}, this.getTokenBalance(formattedBalance, shorten)),
h('div.token-amount', {}, h(UserPreferencedCurrencyDisplay, {
value: balanceValue,
type: PRIMARY,
ethNumberOfDecimals: 3,
})),
showFiat && h(CurrencyDisplay, {
showFiat && h(UserPreferencedCurrencyDisplay, {
value: balanceValue,
type: SECONDARY,
ethNumberOfDecimals: 3,
}),
])
}
BalanceComponent.prototype.getTokenBalance = function (formattedBalance, shorten) {
const balanceObj = generateBalanceObject(formattedBalance, shorten ? 1 : 3)
const balanceValue = shorten ? balanceObj.shortBalance : balanceObj.balance
const label = balanceObj.label
return `${balanceValue} ${label}`
}
BalanceComponent.prototype.getFiatDisplayNumber = function (formattedBalance, conversionRate) {
if (formattedBalance === 'None') return formattedBalance
if (conversionRate === 0) return 'N/A'

@ -1,16 +1,19 @@
import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'
import { PRIMARY, SECONDARY } from '../../../constants/common'
const ConfirmDetailRow = props => {
const {
label,
fiatText,
ethText,
primaryText,
secondaryText,
onHeaderClick,
fiatTextColor,
primaryValueTextColor,
headerText,
headerTextClassName,
value,
} = props
return (
@ -25,28 +28,57 @@ const ConfirmDetailRow = props => {
>
{ headerText }
</div>
{
primaryText
? (
<div
className="confirm-detail-row__fiat"
style={{ color: fiatTextColor }}
className="confirm-detail-row__primary"
style={{ color: primaryValueTextColor }}
>
{ fiatText }
{ primaryText }
</div>
<div className="confirm-detail-row__eth">
{ ethText }
) : (
<UserPreferencedCurrencyDisplay
className="confirm-detail-row__primary"
type={PRIMARY}
value={value}
showEthLogo
ethLogoHeight="18"
style={{ color: primaryValueTextColor }}
hideLabel
/>
)
}
{
secondaryText
? (
<div className="confirm-detail-row__secondary">
{ secondaryText }
</div>
) : (
<UserPreferencedCurrencyDisplay
className="confirm-detail-row__secondary"
type={SECONDARY}
value={value}
showEthLogo
hideLabel
/>
)
}
</div>
</div>
)
}
ConfirmDetailRow.propTypes = {
label: PropTypes.string,
fiatText: PropTypes.string,
ethText: PropTypes.string,
fiatTextColor: PropTypes.string,
onHeaderClick: PropTypes.func,
headerText: PropTypes.string,
headerTextClassName: PropTypes.string,
label: PropTypes.string,
onHeaderClick: PropTypes.func,
primaryValueTextColor: PropTypes.string,
primaryText: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
secondaryText: PropTypes.string,
value: PropTypes.string,
}
export default ConfirmDetailRow

@ -18,18 +18,14 @@
min-width: 0;
}
&__fiat {
&__primary {
font-size: 1.5rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
justify-content: flex-end;
}
&__eth {
&__secondary {
color: $oslo-gray;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
justify-content: flex-end;
}
&__header-text {

@ -12,17 +12,19 @@ describe('Confirm Detail Row Component', function () {
let wrapper
beforeEach(() => {
wrapper = shallow(<ConfirmDetailRow
wrapper = shallow(
<ConfirmDetailRow
errorType={'mockErrorType'}
label={'mockLabel'}
showError={false}
fiatText = {'mockFiatText'}
ethText = {'mockEthText'}
fiatTextColor= {'mockColor'}
primaryText = {'mockFiatText'}
secondaryText = {'mockEthText'}
primaryValueTextColor= {'mockColor'}
onHeaderClick= {propsMethodSpies.onHeaderClick}
headerText = {'mockHeaderText'}
headerTextClassName = {'mockHeaderClass'}
/>)
/>
)
})
describe('render', () => {
@ -38,16 +40,16 @@ describe('Confirm Detail Row Component', function () {
assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__header-text').childAt(0).text(), 'mockHeaderText')
})
it('should render the fiatText as a child of the confirm-detail-row__fiat', () => {
assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__fiat').childAt(0).text(), 'mockFiatText')
it('should render the primaryText as a child of the confirm-detail-row__primary', () => {
assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__primary').childAt(0).text(), 'mockFiatText')
})
it('should render the ethText as a child of the confirm-detail-row__eth', () => {
assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__eth').childAt(0).text(), 'mockEthText')
it('should render the ethText as a child of the confirm-detail-row__secondary', () => {
assert.equal(wrapper.find('.confirm-detail-row__details > .confirm-detail-row__secondary').childAt(0).text(), 'mockEthText')
})
it('should set the fiatTextColor on confirm-detail-row__fiat', () => {
assert.equal(wrapper.find('.confirm-detail-row__fiat').props().style.color, 'mockColor')
it('should set the fiatTextColor on confirm-detail-row__primary', () => {
assert.equal(wrapper.find('.confirm-detail-row__primary').props().style.color, 'mockColor')
})
it('should assure the confirm-detail-row__header-text classname is correct', () => {
@ -58,7 +60,5 @@ describe('Confirm Detail Row Component', function () {
wrapper.find('.confirm-detail-row__header-text').props().onClick()
assert.equal(assert.equal(propsMethodSpies.onHeaderClick.callCount, 1))
})
})
})

@ -17,9 +17,10 @@ export default class ConfirmPageContainerContent extends Component {
nonce: PropTypes.string,
assetImage: PropTypes.string,
subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
subtitleComponent: PropTypes.node,
summaryComponent: PropTypes.node,
title: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
titleComponent: PropTypes.func,
titleComponent: PropTypes.node,
warning: PropTypes.string,
}
@ -54,7 +55,9 @@ export default class ConfirmPageContainerContent extends Component {
errorKey,
errorMessage,
title,
titleComponent,
subtitle,
subtitleComponent,
hideSubtitle,
identiconAddress,
nonce,
@ -80,7 +83,9 @@ export default class ConfirmPageContainerContent extends Component {
})}
action={action}
title={title}
titleComponent={titleComponent}
subtitle={subtitle}
subtitleComponent={subtitleComponent}
hideSubtitle={hideSubtitle}
identiconAddress={identiconAddress}
nonce={nonce}

@ -4,7 +4,18 @@ import classnames from 'classnames'
import Identicon from '../../../identicon'
const ConfirmPageContainerSummary = props => {
const { action, title, subtitle, hideSubtitle, className, identiconAddress, nonce, assetImage } = props
const {
action,
title,
titleComponent,
subtitle,
subtitleComponent,
hideSubtitle,
className,
identiconAddress,
nonce,
assetImage,
} = props
return (
<div className={classnames('confirm-page-container-summary', className)}>
@ -32,12 +43,12 @@ const ConfirmPageContainerSummary = props => {
)
}
<div className="confirm-page-container-summary__title-text">
{ title }
{ titleComponent || title }
</div>
</div>
{
hideSubtitle || <div className="confirm-page-container-summary__subtitle">
{ subtitle }
{ subtitleComponent || subtitle }
</div>
}
</div>
@ -47,7 +58,9 @@ const ConfirmPageContainerSummary = props => {
ConfirmPageContainerSummary.propTypes = {
action: PropTypes.string,
title: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
titleComponent: PropTypes.node,
subtitle: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
subtitleComponent: PropTypes.node,
hideSubtitle: PropTypes.bool,
className: PropTypes.string,
identiconAddress: PropTypes.string,

@ -16,8 +16,9 @@ export default class ConfirmPageContainer extends Component {
onEdit: PropTypes.func,
showEdit: PropTypes.bool,
subtitle: PropTypes.string,
subtitleComponent: PropTypes.node,
title: PropTypes.string,
titleComponent: PropTypes.func,
titleComponent: PropTypes.node,
// Sender to Recipient
fromAddress: PropTypes.string,
fromName: PropTypes.string,
@ -65,6 +66,7 @@ export default class ConfirmPageContainer extends Component {
title,
titleComponent,
subtitle,
subtitleComponent,
hideSubtitle,
summaryComponent,
detailsComponent,
@ -101,6 +103,7 @@ export default class ConfirmPageContainer extends Component {
title={title}
titleComponent={titleComponent}
subtitle={subtitle}
subtitleComponent={subtitleComponent}
hideSubtitle={hideSubtitle}
summaryComponent={summaryComponent}
detailsComponent={detailsComponent}

@ -1,5 +1,6 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import { ETH, GWEI } from '../../constants/common'
export default class CurrencyDisplay extends PureComponent {
@ -7,6 +8,8 @@ export default class CurrencyDisplay extends PureComponent {
className: PropTypes.string,
displayValue: PropTypes.string,
prefix: PropTypes.string,
prefixComponent: PropTypes.node,
style: PropTypes.object,
// Used in container
currency: PropTypes.oneOf([ETH]),
denomination: PropTypes.oneOf([GWEI]),
@ -16,15 +19,17 @@ export default class CurrencyDisplay extends PureComponent {
}
render () {
const { className, displayValue, prefix } = this.props
const { className, displayValue, prefix, prefixComponent, style } = this.props
const text = `${prefix || ''}${displayValue}`
return (
<div
className={className}
className={classnames('currency-display-component', className)}
style={style}
title={text}
>
{ text }
{ prefixComponent}
<span className="currency-display-component__text">{ text }</span>
</div>
)
}

@ -2,10 +2,26 @@ import { connect } from 'react-redux'
import CurrencyDisplay from './currency-display.component'
import { getValueFromWeiHex, formatCurrency } from '../../helpers/confirm-transaction/util'
const mapStateToProps = (state, ownProps) => {
const { value, numberOfDecimals = 2, currency, denomination, hideLabel } = ownProps
const mapStateToProps = state => {
const { metamask: { currentCurrency, conversionRate } } = state
return {
currentCurrency,
conversionRate,
}
}
const mergeProps = (stateProps, dispatchProps, ownProps) => {
const { currentCurrency, conversionRate, ...restStateProps } = stateProps
const {
value,
numberOfDecimals = 2,
currency,
denomination,
hideLabel,
...restOwnProps
} = ownProps
const toCurrency = currency || currentCurrency
const convertedValue = getValueFromWeiHex({
value, toCurrency, conversionRate, numberOfDecimals, toDenomination: denomination,
@ -14,8 +30,11 @@ const mapStateToProps = (state, ownProps) => {
const displayValue = hideLabel ? formattedValue : `${formattedValue} ${toCurrency.toUpperCase()}`
return {
...restStateProps,
...dispatchProps,
...restOwnProps,
displayValue,
}
}
export default connect(mapStateToProps)(CurrencyDisplay)
export default connect(mapStateToProps, null, mergeProps)(CurrencyDisplay)

@ -0,0 +1,10 @@
.currency-display-component {
display: flex;
align-items: center;
&__text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}

@ -1,12 +1,13 @@
import assert from 'assert'
import proxyquire from 'proxyquire'
let mapStateToProps
let mapStateToProps, mergeProps
proxyquire('../currency-display.container.js', {
'react-redux': {
connect: ms => {
connect: (ms, md, mp) => {
mapStateToProps = ms
mergeProps = mp
return () => ({})
},
},
@ -22,6 +23,20 @@ describe('CurrencyDisplay container', () => {
},
}
assert.deepEqual(mapStateToProps(mockState), {
conversionRate: 280.45,
currentCurrency: 'usd',
})
})
})
describe('mergeProps()', () => {
it('should return the correct props', () => {
const mockStateProps = {
conversionRate: 280.45,
currentCurrency: 'usd',
}
const tests = [
{
props: {
@ -98,7 +113,7 @@ describe('CurrencyDisplay container', () => {
]
tests.forEach(({ props, result }) => {
assert.deepEqual(mapStateToProps(mockState, props), result)
assert.deepEqual(mergeProps(mockStateProps, {}, { ...props }), result)
})
})
})

@ -0,0 +1,120 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import UnitInput from '../unit-input'
import CurrencyDisplay from '../currency-display'
import { getValueFromWeiHex, getWeiHexFromDecimalValue } from '../../helpers/conversions.util'
import { ETH } from '../../constants/common'
/**
* Component that allows user to enter currency values as a number, and props receive a converted
* hex value in WEI. props.value, used as a default or forced value, should be a hex value, which
* gets converted into a decimal value depending on the currency (ETH or Fiat).
*/
export default class CurrencyInput extends PureComponent {
static propTypes = {
conversionRate: PropTypes.number,
currentCurrency: PropTypes.string,
onChange: PropTypes.func,
onBlur: PropTypes.func,
suffix: PropTypes.string,
useFiat: PropTypes.bool,
value: PropTypes.string,
}
constructor (props) {
super(props)
const { value: hexValue } = props
const decimalValue = hexValue ? this.getDecimalValue(props) : 0
this.state = {
decimalValue,
hexValue,
}
}
componentDidUpdate (prevProps) {
const { value: prevPropsHexValue } = prevProps
const { value: propsHexValue } = this.props
const { hexValue: stateHexValue } = this.state
if (prevPropsHexValue !== propsHexValue && propsHexValue !== stateHexValue) {
const decimalValue = this.getDecimalValue(this.props)
this.setState({ hexValue: propsHexValue, decimalValue })
}
}
getDecimalValue (props) {
const { value: hexValue, useFiat, currentCurrency, conversionRate } = props
const decimalValueString = useFiat
? getValueFromWeiHex({
value: hexValue, toCurrency: currentCurrency, conversionRate, numberOfDecimals: 2,
})
: getValueFromWeiHex({
value: hexValue, toCurrency: ETH, numberOfDecimals: 6,
})
return Number(decimalValueString) || 0
}
handleChange = decimalValue => {
const { useFiat, currentCurrency: fromCurrency, conversionRate, onChange } = this.props
const hexValue = useFiat
? getWeiHexFromDecimalValue({
value: decimalValue, fromCurrency, conversionRate, invertConversionRate: true,
})
: getWeiHexFromDecimalValue({
value: decimalValue, fromCurrency: ETH, fromDenomination: ETH, conversionRate,
})
this.setState({ hexValue, decimalValue })
onChange(hexValue)
}
handleBlur = () => {
this.props.onBlur(this.state.hexValue)
}
renderConversionComponent () {
const { useFiat, currentCurrency } = this.props
const { hexValue } = this.state
let currency, numberOfDecimals
if (useFiat) {
// Display ETH
currency = ETH
numberOfDecimals = 6
} else {
// Display Fiat
currency = currentCurrency
numberOfDecimals = 2
}
return (
<CurrencyDisplay
className="currency-input__conversion-component"
currency={currency}
value={hexValue}
numberOfDecimals={numberOfDecimals}
/>
)
}
render () {
const { suffix, ...restProps } = this.props
const { decimalValue } = this.state
return (
<UnitInput
{...restProps}
suffix={suffix}
onChange={this.handleChange}
onBlur={this.handleBlur}
value={decimalValue}
>
{ this.renderConversionComponent() }
</UnitInput>
)
}
}

@ -0,0 +1,27 @@
import { connect } from 'react-redux'
import CurrencyInput from './currency-input.component'
import { ETH } from '../../constants/common'
const mapStateToProps = state => {
const { metamask: { currentCurrency, conversionRate } } = state
return {
currentCurrency,
conversionRate,
}
}
const mergeProps = (stateProps, dispatchProps, ownProps) => {
const { currentCurrency } = stateProps
const { useFiat } = ownProps
const suffix = useFiat ? currentCurrency.toUpperCase() : ETH
return {
...stateProps,
...dispatchProps,
...ownProps,
suffix,
}
}
export default connect(mapStateToProps, null, mergeProps)(CurrencyInput)

@ -0,0 +1 @@
export { default } from './currency-input.container'

@ -0,0 +1,7 @@
.currency-input {
&__conversion-component {
font-size: 12px;
line-height: 12px;
padding-left: 1px;
}
}

@ -0,0 +1,239 @@
import React from 'react'
import assert from 'assert'
import { shallow, mount } from 'enzyme'
import sinon from 'sinon'
import { Provider } from 'react-redux'
import configureMockStore from 'redux-mock-store'
import CurrencyInput from '../currency-input.component'
import UnitInput from '../../unit-input'
import CurrencyDisplay from '../../currency-display'
describe('CurrencyInput Component', () => {
describe('rendering', () => {
it('should render properly without a suffix', () => {
const wrapper = shallow(
<CurrencyInput />
)
assert.ok(wrapper)
assert.equal(wrapper.find(UnitInput).length, 1)
})
it('should render properly with a suffix', () => {
const mockStore = {
metamask: {
currentCurrency: 'usd',
conversionRate: 231.06,
},
}
const store = configureMockStore()(mockStore)
const wrapper = mount(
<Provider store={store}>
<CurrencyInput
suffix="ETH"
/>
</Provider>
)
assert.ok(wrapper)
assert.equal(wrapper.find('.unit-input__suffix').length, 1)
assert.equal(wrapper.find('.unit-input__suffix').text(), 'ETH')
assert.equal(wrapper.find(CurrencyDisplay).length, 1)
})
it('should render properly with an ETH value', () => {
const mockStore = {
metamask: {
currentCurrency: 'usd',
conversionRate: 231.06,
},
}
const store = configureMockStore()(mockStore)
const wrapper = mount(
<Provider store={store}>
<CurrencyInput
value="de0b6b3a7640000"
suffix="ETH"
currentCurrency="usd"
conversionRate={231.06}
/>
</Provider>
)
assert.ok(wrapper)
const currencyInputInstance = wrapper.find(CurrencyInput).at(0).instance()
assert.equal(currencyInputInstance.state.decimalValue, 1)
assert.equal(currencyInputInstance.state.hexValue, 'de0b6b3a7640000')
assert.equal(wrapper.find('.unit-input__suffix').length, 1)
assert.equal(wrapper.find('.unit-input__suffix').text(), 'ETH')
assert.equal(wrapper.find('.unit-input__input').props().value, '1')
assert.equal(wrapper.find('.currency-display-component').text(), '$231.06 USD')
})
it('should render properly with a fiat value', () => {
const mockStore = {
metamask: {
currentCurrency: 'usd',
conversionRate: 231.06,
},
}
const store = configureMockStore()(mockStore)
const wrapper = mount(
<Provider store={store}>
<CurrencyInput
value="f602f2234d0ea"
suffix="USD"
useFiat
currentCurrency="usd"
conversionRate={231.06}
/>
</Provider>
)
assert.ok(wrapper)
const currencyInputInstance = wrapper.find(CurrencyInput).at(0).instance()
assert.equal(currencyInputInstance.state.decimalValue, 1)
assert.equal(currencyInputInstance.state.hexValue, 'f602f2234d0ea')
assert.equal(wrapper.find('.unit-input__suffix').length, 1)
assert.equal(wrapper.find('.unit-input__suffix').text(), 'USD')
assert.equal(wrapper.find('.unit-input__input').props().value, '1')
assert.equal(wrapper.find('.currency-display-component').text(), '0.004328 ETH')
})
})
describe('handling actions', () => {
const handleChangeSpy = sinon.spy()
const handleBlurSpy = sinon.spy()
afterEach(() => {
handleChangeSpy.resetHistory()
handleBlurSpy.resetHistory()
})
it('should call onChange and onBlur on input changes with the hex value for ETH', () => {
const mockStore = {
metamask: {
currentCurrency: 'usd',
conversionRate: 231.06,
},
}
const store = configureMockStore()(mockStore)
const wrapper = mount(
<Provider store={store}>
<CurrencyInput
onChange={handleChangeSpy}
onBlur={handleBlurSpy}
suffix="ETH"
currentCurrency="usd"
conversionRate={231.06}
/>
</Provider>
)
assert.ok(wrapper)
assert.equal(handleChangeSpy.callCount, 0)
assert.equal(handleBlurSpy.callCount, 0)
const currencyInputInstance = wrapper.find(CurrencyInput).at(0).instance()
assert.equal(currencyInputInstance.state.decimalValue, 0)
assert.equal(currencyInputInstance.state.hexValue, undefined)
assert.equal(wrapper.find('.currency-display-component').text(), '$0.00 USD')
const input = wrapper.find('input')
assert.equal(input.props().value, 0)
input.simulate('change', { target: { value: 1 } })
assert.equal(handleChangeSpy.callCount, 1)
assert.ok(handleChangeSpy.calledWith('de0b6b3a7640000'))
assert.equal(wrapper.find('.currency-display-component').text(), '$231.06 USD')
assert.equal(currencyInputInstance.state.decimalValue, 1)
assert.equal(currencyInputInstance.state.hexValue, 'de0b6b3a7640000')
assert.equal(handleBlurSpy.callCount, 0)
input.simulate('blur')
assert.equal(handleBlurSpy.callCount, 1)
assert.ok(handleBlurSpy.calledWith('de0b6b3a7640000'))
})
it('should call onChange and onBlur on input changes with the hex value for fiat', () => {
const mockStore = {
metamask: {
currentCurrency: 'usd',
conversionRate: 231.06,
},
}
const store = configureMockStore()(mockStore)
const wrapper = mount(
<Provider store={store}>
<CurrencyInput
onChange={handleChangeSpy}
onBlur={handleBlurSpy}
suffix="USD"
currentCurrency="usd"
conversionRate={231.06}
useFiat
/>
</Provider>
)
assert.ok(wrapper)
assert.equal(handleChangeSpy.callCount, 0)
assert.equal(handleBlurSpy.callCount, 0)
const currencyInputInstance = wrapper.find(CurrencyInput).at(0).instance()
assert.equal(currencyInputInstance.state.decimalValue, 0)
assert.equal(currencyInputInstance.state.hexValue, undefined)
assert.equal(wrapper.find('.currency-display-component').text(), '0 ETH')
const input = wrapper.find('input')
assert.equal(input.props().value, 0)
input.simulate('change', { target: { value: 1 } })
assert.equal(handleChangeSpy.callCount, 1)
assert.ok(handleChangeSpy.calledWith('f602f2234d0ea'))
assert.equal(wrapper.find('.currency-display-component').text(), '0.004328 ETH')
assert.equal(currencyInputInstance.state.decimalValue, 1)
assert.equal(currencyInputInstance.state.hexValue, 'f602f2234d0ea')
assert.equal(handleBlurSpy.callCount, 0)
input.simulate('blur')
assert.equal(handleBlurSpy.callCount, 1)
assert.ok(handleBlurSpy.calledWith('f602f2234d0ea'))
})
it('should change the state and pass in a new decimalValue when props.value changes', () => {
const mockStore = {
metamask: {
currentCurrency: 'usd',
conversionRate: 231.06,
},
}
const store = configureMockStore()(mockStore)
const wrapper = shallow(
<Provider store={store}>
<CurrencyInput
onChange={handleChangeSpy}
onBlur={handleBlurSpy}
suffix="USD"
currentCurrency="usd"
conversionRate={231.06}
useFiat
/>
</Provider>
)
assert.ok(wrapper)
const currencyInputInstance = wrapper.find(CurrencyInput).dive()
assert.equal(currencyInputInstance.state('decimalValue'), 0)
assert.equal(currencyInputInstance.state('hexValue'), undefined)
assert.equal(currencyInputInstance.find(UnitInput).props().value, 0)
currencyInputInstance.setProps({ value: '1ec05e43e72400' })
currencyInputInstance.update()
assert.equal(currencyInputInstance.state('decimalValue'), 2)
assert.equal(currencyInputInstance.state('hexValue'), '1ec05e43e72400')
assert.equal(currencyInputInstance.find(UnitInput).props().value, 2)
})
})
})

@ -0,0 +1,55 @@
import assert from 'assert'
import proxyquire from 'proxyquire'
let mapStateToProps, mergeProps
proxyquire('../currency-input.container.js', {
'react-redux': {
connect: (ms, md, mp) => {
mapStateToProps = ms
mergeProps = mp
return () => ({})
},
},
})
describe('CurrencyInput container', () => {
describe('mapStateToProps()', () => {
it('should return the correct props', () => {
const mockState = {
metamask: {
conversionRate: 280.45,
currentCurrency: 'usd',
},
}
assert.deepEqual(mapStateToProps(mockState), {
conversionRate: 280.45,
currentCurrency: 'usd',
})
})
})
describe('mergeProps()', () => {
it('should return the correct props', () => {
const mockStateProps = {
conversionRate: 280.45,
currentCurrency: 'usd',
}
const mockDispatchProps = {}
assert.deepEqual(mergeProps(mockStateProps, mockDispatchProps, { useFiat: true }), {
conversionRate: 280.45,
currentCurrency: 'usd',
useFiat: true,
suffix: 'USD',
})
assert.deepEqual(mergeProps(mockStateProps, mockDispatchProps, {}), {
conversionRate: 280.45,
currentCurrency: 'usd',
suffix: 'ETH',
})
})
})
})

@ -1,11 +1,17 @@
@import './app-header/index';
@import './add-token-button/index';
@import './button-group/index';
@import './card/index';
@import './confirm-page-container/index';
@import './currency-input/index';
@import './currency-display/index';
@import './error-message/index';
@import './export-text-container/index';
@ -49,3 +55,5 @@
@import './app-header/index';
@import './sidebars/index';
@import './unit-input/index';

@ -1,7 +1,7 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import CurrencyDisplay from '../../../currency-display'
import { ETH } from '../../../../constants/common'
import UserPreferencedCurrencyDisplay from '../../../user-preferenced-currency-display'
import { PRIMARY, SECONDARY } from '../../../../constants/common'
export default class CancelTransaction extends PureComponent {
static propTypes = {
@ -13,15 +13,15 @@ export default class CancelTransaction extends PureComponent {
return (
<div className="cancel-transaction-gas-fee">
<CurrencyDisplay
<UserPreferencedCurrencyDisplay
className="cancel-transaction-gas-fee__eth"
currency={ETH}
value={value}
numberOfDecimals={6}
type={PRIMARY}
/>
<CurrencyDisplay
<UserPreferencedCurrencyDisplay
className="cancel-transaction-gas-fee__fiat"
value={value}
type={SECONDARY}
/>
</div>
)

@ -2,7 +2,7 @@ import React from 'react'
import assert from 'assert'
import { shallow } from 'enzyme'
import CancelTransactionGasFee from '../cancel-transaction-gas-fee.component'
import CurrencyDisplay from '../../../../currency-display'
import UserPreferencedCurrencyDisplay from '../../../../user-preferenced-currency-display'
describe('CancelTransactionGasFee Component', () => {
it('should render', () => {
@ -13,12 +13,11 @@ describe('CancelTransactionGasFee Component', () => {
)
assert.ok(wrapper)
assert.equal(wrapper.find(CurrencyDisplay).length, 2)
const ethDisplay = wrapper.find(CurrencyDisplay).at(0)
const fiatDisplay = wrapper.find(CurrencyDisplay).at(1)
assert.equal(wrapper.find(UserPreferencedCurrencyDisplay).length, 2)
const ethDisplay = wrapper.find(UserPreferencedCurrencyDisplay).at(0)
const fiatDisplay = wrapper.find(UserPreferencedCurrencyDisplay).at(1)
assert.equal(ethDisplay.props().value, '0x3b9aca00')
assert.equal(ethDisplay.props().currency, 'ETH')
assert.equal(ethDisplay.props().className, 'cancel-transaction-gas-fee__eth')
assert.equal(fiatDisplay.props().value, '0x3b9aca00')

@ -1,12 +1,15 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import ConfirmTransactionBase from '../confirm-transaction-base'
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'
import {
formatCurrency,
convertTokenToFiat,
addFiat,
roundExponential,
} from '../../../helpers/confirm-transaction/util'
import { getWeiHexFromDecimalValue } from '../../../helpers/conversions.util'
import { ETH, PRIMARY } from '../../../constants/common'
export default class ConfirmTokenTransactionBase extends Component {
static contextTypes = {
@ -36,19 +39,48 @@ export default class ConfirmTokenTransactionBase extends Component {
})
}
getSubtitle () {
const { currentCurrency, contractExchangeRate } = this.props
renderSubtitleComponent () {
const { contractExchangeRate, tokenAmount } = this.props
if (typeof contractExchangeRate === 'undefined') {
return this.context.t('noConversionRateAvailable')
} else {
const fiatTransactionAmount = this.getFiatTransactionAmount()
const roundedFiatTransactionAmount = roundExponential(fiatTransactionAmount)
return formatCurrency(roundedFiatTransactionAmount, currentCurrency)
const decimalEthValue = (tokenAmount * contractExchangeRate) || 0
const hexWeiValue = getWeiHexFromDecimalValue({
value: decimalEthValue,
fromCurrency: ETH,
fromDenomination: ETH,
})
return typeof contractExchangeRate === 'undefined'
? (
<span>
{ this.context.t('noConversionRateAvailable') }
</span>
) : (
<UserPreferencedCurrencyDisplay
value={hexWeiValue}
type={PRIMARY}
showEthLogo
hideLabel
/>
)
}
renderPrimaryTotalTextOverride () {
const { tokenAmount, tokenSymbol, ethTransactionTotal } = this.props
const tokensText = `${tokenAmount} ${tokenSymbol}`
return (
<div>
<span>{ `${tokensText} + ` }</span>
<img
src="/images/eth.svg"
height="18"
/>
<span>{ ethTransactionTotal }</span>
</div>
)
}
getFiatTotalTextOverride () {
getSecondaryTotalTextOverride () {
const { fiatTransactionTotal, currentCurrency, contractExchangeRate } = this.props
if (typeof contractExchangeRate === 'undefined') {
@ -67,7 +99,6 @@ export default class ConfirmTokenTransactionBase extends Component {
tokenAddress,
tokenSymbol,
tokenAmount,
ethTransactionTotal,
...restProps
} = this.props
@ -78,9 +109,9 @@ export default class ConfirmTokenTransactionBase extends Component {
toAddress={toAddress}
identiconAddress={tokenAddress}
title={tokensText}
subtitle={this.getSubtitle()}
ethTotalTextOverride={`${tokensText} + \u2666 ${ethTransactionTotal}`}
fiatTotalTextOverride={this.getFiatTotalTextOverride()}
subtitleComponent={this.renderSubtitleComponent()}
primaryTotalTextOverride={this.renderPrimaryTotalTextOverride()}
secondaryTotalTextOverride={this.getSecondaryTotalTextOverride()}
{...restProps}
/>
)

@ -1,7 +1,6 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import ConfirmPageContainer, { ConfirmDetailRow } from '../../confirm-page-container'
import { formatCurrency } from '../../../helpers/confirm-transaction/util'
import { isBalanceSufficient } from '../../send/send.utils'
import { DEFAULT_ROUTE } from '../../../routes'
import {
@ -9,6 +8,8 @@ import {
TRANSACTION_ERROR_KEY,
} from '../../../constants/error-keys'
import { CONFIRMED_STATUS, DROPPED_STATUS } from '../../../constants/transactions'
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'
import { PRIMARY, SECONDARY } from '../../../constants/common'
export default class ConfirmTransactionBase extends Component {
static contextTypes = {
@ -36,7 +37,9 @@ export default class ConfirmTransactionBase extends Component {
fiatTransactionTotal: PropTypes.string,
fromAddress: PropTypes.string,
fromName: PropTypes.string,
hexGasTotal: PropTypes.string,
hexTransactionAmount: PropTypes.string,
hexTransactionFee: PropTypes.string,
hexTransactionTotal: PropTypes.string,
isTxReprice: PropTypes.bool,
methodData: PropTypes.object,
nonce: PropTypes.string,
@ -59,8 +62,8 @@ export default class ConfirmTransactionBase extends Component {
detailsComponent: PropTypes.node,
errorKey: PropTypes.string,
errorMessage: PropTypes.string,
ethTotalTextOverride: PropTypes.string,
fiatTotalTextOverride: PropTypes.string,
primaryTotalTextOverride: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
secondaryTotalTextOverride: PropTypes.string,
hideData: PropTypes.bool,
hideDetails: PropTypes.bool,
hideSubtitle: PropTypes.bool,
@ -70,8 +73,10 @@ export default class ConfirmTransactionBase extends Component {
onEditGas: PropTypes.func,
onSubmit: PropTypes.func,
subtitle: PropTypes.string,
subtitleComponent: PropTypes.node,
summaryComponent: PropTypes.node,
title: PropTypes.string,
titleComponent: PropTypes.node,
valid: PropTypes.bool,
warning: PropTypes.string,
}
@ -105,7 +110,7 @@ export default class ConfirmTransactionBase extends Component {
const {
balance,
conversionRate,
hexGasTotal,
hexTransactionFee,
txData: {
simulationFails,
txParams: {
@ -116,7 +121,7 @@ export default class ConfirmTransactionBase extends Component {
const insufficientBalance = balance && !isBalanceSufficient({
amount,
gasTotal: hexGasTotal || '0x0',
gasTotal: hexTransactionFee || '0x0',
balance,
conversionRate,
})
@ -153,13 +158,10 @@ export default class ConfirmTransactionBase extends Component {
renderDetails () {
const {
detailsComponent,
fiatTransactionFee,
ethTransactionFee,
currentCurrency,
fiatTransactionTotal,
ethTransactionTotal,
fiatTotalTextOverride,
ethTotalTextOverride,
primaryTotalTextOverride,
secondaryTotalTextOverride,
hexTransactionFee,
hexTransactionTotal,
hideDetails,
} = this.props
@ -167,16 +169,13 @@ export default class ConfirmTransactionBase extends Component {
return null
}
const formattedCurrency = formatCurrency(fiatTransactionTotal, currentCurrency)
return (
detailsComponent || (
<div className="confirm-page-container-content__details">
<div className="confirm-page-container-content__gas-fee">
<ConfirmDetailRow
label="Gas Fee"
fiatText={formatCurrency(fiatTransactionFee, currentCurrency)}
ethText={`\u2666 ${ethTransactionFee}`}
value={hexTransactionFee}
headerText="Edit"
headerTextClassName="confirm-detail-row__header-text--edit"
onHeaderClick={() => this.handleEditGas()}
@ -185,11 +184,12 @@ export default class ConfirmTransactionBase extends Component {
<div>
<ConfirmDetailRow
label="Total"
fiatText={fiatTotalTextOverride || formattedCurrency}
ethText={ethTotalTextOverride || `\u2666 ${ethTransactionTotal}`}
value={hexTransactionTotal}
primaryText={primaryTotalTextOverride}
secondaryText={secondaryTotalTextOverride}
headerText="Amount + Gas Fee"
headerTextClassName="confirm-detail-row__header-text--total"
fiatTextColor="#2f9ae0"
primaryValueTextColor="#2f9ae0"
/>
</div>
</div>
@ -311,6 +311,43 @@ export default class ConfirmTransactionBase extends Component {
}
}
renderTitleComponent () {
const { title, titleComponent, hexTransactionAmount } = this.props
// Title string passed in by props takes priority
if (title) {
return null
}
return titleComponent || (
<UserPreferencedCurrencyDisplay
value={hexTransactionAmount}
type={PRIMARY}
showEthLogo
ethLogoHeight="26"
hideLabel
/>
)
}
renderSubtitleComponent () {
const { subtitle, subtitleComponent, hexTransactionAmount } = this.props
// Subtitle string passed in by props takes priority
if (subtitle) {
return null
}
return subtitleComponent || (
<UserPreferencedCurrencyDisplay
value={hexTransactionAmount}
type={SECONDARY}
showEthLogo
hideLabel
/>
)
}
render () {
const {
isTxReprice,
@ -319,12 +356,9 @@ export default class ConfirmTransactionBase extends Component {
toName,
toAddress,
methodData,
ethTransactionAmount,
fiatTransactionAmount,
valid: propsValid = true,
errorMessage,
errorKey: propsErrorKey,
currentCurrency,
action,
title,
subtitle,
@ -341,7 +375,6 @@ export default class ConfirmTransactionBase extends Component {
const { submitting, submitError } = this.state
const { name } = methodData
const fiatConvertedAmount = formatCurrency(fiatTransactionAmount, currentCurrency)
const { valid, errorKey } = this.getErrorKey()
return (
@ -352,8 +385,10 @@ export default class ConfirmTransactionBase extends Component {
toAddress={toAddress}
showEdit={onEdit && !isTxReprice}
action={action || name || this.context.t('unknownFunction')}
title={title || `${fiatConvertedAmount} ${currentCurrency.toUpperCase()}`}
subtitle={subtitle || `\u2666 ${ethTransactionAmount}`}
title={title}
titleComponent={this.renderTitleComponent()}
subtitle={subtitle}
subtitleComponent={this.renderSubtitleComponent()}
hideSubtitle={hideSubtitle}
summaryComponent={summaryComponent}
detailsComponent={this.renderDetails()}

@ -36,7 +36,9 @@ const mapStateToProps = (state, props) => {
fiatTransactionAmount,
fiatTransactionFee,
fiatTransactionTotal,
hexGasTotal,
hexTransactionAmount,
hexTransactionFee,
hexTransactionTotal,
tokenData,
methodData,
txData,
@ -87,7 +89,9 @@ const mapStateToProps = (state, props) => {
fiatTransactionAmount,
fiatTransactionFee,
fiatTransactionTotal,
hexGasTotal,
hexTransactionAmount,
hexTransactionFee,
hexTransactionTotal,
txData,
tokenData,
methodData,

@ -48,4 +48,22 @@
border-color: $ecstasy;
}
}
&__radio-buttons {
display: flex;
align-items: center;
}
&__radio-button {
display: flex;
align-items: center;
&:not(:last-child) {
margin-right: 16px;
}
}
&__radio-label {
padding-left: 4px;
}
}

@ -55,6 +55,8 @@ export default class SettingsTab extends PureComponent {
sendHexData: PropTypes.bool,
currentCurrency: PropTypes.string,
conversionDate: PropTypes.number,
useETHAsPrimaryCurrency: PropTypes.bool,
setUseETHAsPrimaryCurrencyPreference: PropTypes.func,
}
state = {
@ -339,6 +341,56 @@ export default class SettingsTab extends PureComponent {
)
}
renderUseEthAsPrimaryCurrency () {
const { t } = this.context
const { useETHAsPrimaryCurrency, setUseETHAsPrimaryCurrencyPreference } = this.props
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{ t('primaryCurrencySetting') }</span>
<div className="settings-page__content-description">
{ t('primaryCurrencySettingDescription') }
</div>
</div>
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<div className="settings-tab__radio-buttons">
<div className="settings-tab__radio-button">
<input
type="radio"
id="eth-primary-currency"
onChange={() => setUseETHAsPrimaryCurrencyPreference(true)}
checked={Boolean(useETHAsPrimaryCurrency)}
/>
<label
htmlFor="eth-primary-currency"
className="settings-tab__radio-label"
>
{ t('eth') }
</label>
</div>
<div className="settings-tab__radio-button">
<input
type="radio"
id="fiat-primary-currency"
onChange={() => setUseETHAsPrimaryCurrencyPreference(false)}
checked={!useETHAsPrimaryCurrency}
/>
<label
htmlFor="fiat-primary-currency"
className="settings-tab__radio-label"
>
{ t('fiat') }
</label>
</div>
</div>
</div>
</div>
</div>
)
}
render () {
const { warning, isMascara } = this.props
@ -346,6 +398,7 @@ export default class SettingsTab extends PureComponent {
<div className="settings-page__content">
{ warning && <div className="settings-tab__error">{ warning }</div> }
{ this.renderCurrentConversion() }
{ this.renderUseEthAsPrimaryCurrency() }
{ this.renderCurrentLocale() }
{ this.renderNewRpcUrl() }
{ this.renderStateLogs() }

@ -11,7 +11,9 @@ import {
updateCurrentLocale,
setFeatureFlag,
showModal,
setUseETHAsPrimaryCurrencyPreference,
} from '../../../../actions'
import { preferencesSelector } from '../../../../selectors'
const mapStateToProps = state => {
const { appState: { warning }, metamask } = state
@ -24,6 +26,7 @@ const mapStateToProps = state => {
isMascara,
currentLocale,
} = metamask
const { useETHAsPrimaryCurrency } = preferencesSelector(state)
return {
warning,
@ -34,6 +37,7 @@ const mapStateToProps = state => {
useBlockie,
sendHexData,
provider,
useETHAsPrimaryCurrency,
}
}
@ -50,6 +54,9 @@ const mapDispatchToProps = dispatch => {
},
setHexDataFeatureFlag: shouldShow => dispatch(setFeatureFlag('sendHexData', shouldShow)),
showResetAccountConfirmationModal: () => dispatch(showModal({ name: 'CONFIRM_RESET_ACCOUNT' })),
setUseETHAsPrimaryCurrencyPreference: value => {
return dispatch(setUseETHAsPrimaryCurrencyPreference(value))
},
}
}

@ -2,7 +2,8 @@ import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { checksumAddress } from '../../../util'
import Identicon from '../../identicon'
import CurrencyDisplay from '../currency-display'
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'
import { PRIMARY, SECONDARY } from '../../../constants/common'
export default class AccountListItem extends Component {
@ -25,8 +26,6 @@ export default class AccountListItem extends Component {
const {
account,
className,
conversionRate,
currentCurrency,
displayAddress = false,
displayBalance = true,
handleClick,
@ -57,16 +56,20 @@ export default class AccountListItem extends Component {
{ checksumAddress(address) }
</div>}
{displayBalance && <CurrencyDisplay
className="account-list-item__account-balances"
conversionRate={conversionRate}
convertedBalanceClassName="account-list-item__account-secondary-balance"
convertedCurrency={currentCurrency}
primaryBalanceClassName="account-list-item__account-primary-balance"
primaryCurrency="ETH"
readOnly={true}
{
displayBalance && (
<div className="account-list-item__account-balances">
<UserPreferencedCurrencyDisplay
type={PRIMARY}
value={balance}
/>}
/>
<UserPreferencedCurrencyDisplay
type={SECONDARY}
value={balance}
/>
</div>
)
}
</div>)
}

@ -4,7 +4,7 @@ import { shallow } from 'enzyme'
import sinon from 'sinon'
import proxyquire from 'proxyquire'
import Identicon from '../../../identicon'
import CurrencyDisplay from '../../currency-display'
import UserPreferencedCurrencyDisplay from '../../../user-preferenced-currency-display'
const utilsMethodStubs = {
checksumAddress: sinon.stub().returns('mockCheckSumAddress'),
@ -114,17 +114,11 @@ describe('AccountListItem Component', function () {
it('should render a CurrencyDisplay with the correct props if displayBalance is true', () => {
wrapper.setProps({ displayBalance: true })
assert.equal(wrapper.find(CurrencyDisplay).length, 1)
assert.equal(wrapper.find(UserPreferencedCurrencyDisplay).length, 2)
assert.deepEqual(
wrapper.find(CurrencyDisplay).props(),
wrapper.find(UserPreferencedCurrencyDisplay).at(0).props(),
{
className: 'account-list-item__account-balances',
conversionRate: 4,
convertedBalanceClassName: 'account-list-item__account-secondary-balance',
convertedCurrency: 'mockCurrentyCurrency',
primaryBalanceClassName: 'account-list-item__account-primary-balance',
primaryCurrency: 'ETH',
readOnly: true,
type: 'PRIMARY',
value: 'mockBalance',
}
)
@ -132,7 +126,7 @@ describe('AccountListItem Component', function () {
it('should not render a CurrencyDisplay if displayBalance is false', () => {
wrapper.setProps({ displayBalance: false })
assert.equal(wrapper.find(CurrencyDisplay).length, 0)
assert.equal(wrapper.find(UserPreferencedCurrencyDisplay).length, 0)
})
})
})

@ -1,186 +0,0 @@
const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
const { conversionUtil, multiplyCurrencies } = require('../../../conversion-util')
const { removeLeadingZeroes } = require('../send.utils')
const currencyFormatter = require('currency-formatter')
const currencies = require('currency-formatter/currencies')
const ethUtil = require('ethereumjs-util')
const PropTypes = require('prop-types')
CurrencyDisplay.contextTypes = {
t: PropTypes.func,
}
module.exports = CurrencyDisplay
inherits(CurrencyDisplay, Component)
function CurrencyDisplay () {
Component.call(this)
}
function toHexWei (value) {
return conversionUtil(value, {
fromNumericBase: 'dec',
toNumericBase: 'hex',
toDenomination: 'WEI',
})
}
CurrencyDisplay.prototype.componentWillMount = function () {
this.setState({
valueToRender: this.getValueToRender(this.props),
})
}
CurrencyDisplay.prototype.componentWillReceiveProps = function (nextProps) {
const currentValueToRender = this.getValueToRender(this.props)
const newValueToRender = this.getValueToRender(nextProps)
if (currentValueToRender !== newValueToRender) {
this.setState({
valueToRender: newValueToRender,
})
}
}
CurrencyDisplay.prototype.getAmount = function (value) {
const { selectedToken } = this.props
const { decimals } = selectedToken || {}
const multiplier = Math.pow(10, Number(decimals || 0))
const sendAmount = multiplyCurrencies(value || '0', multiplier, {toNumericBase: 'hex'})
return selectedToken
? sendAmount
: toHexWei(value)
}
CurrencyDisplay.prototype.getValueToRender = function ({ selectedToken, conversionRate, value, readOnly }) {
if (value === '0x0') return readOnly ? '0' : ''
const { decimals, symbol } = selectedToken || {}
const multiplier = Math.pow(10, Number(decimals || 0))
return selectedToken
? conversionUtil(ethUtil.addHexPrefix(value), {
fromNumericBase: 'hex',
toNumericBase: 'dec',
toCurrency: symbol,
conversionRate: multiplier,
invertConversionRate: true,
})
: conversionUtil(ethUtil.addHexPrefix(value), {
fromNumericBase: 'hex',
toNumericBase: 'dec',
fromDenomination: 'WEI',
numberOfDecimals: 9,
conversionRate,
})
}
CurrencyDisplay.prototype.getConvertedValueToRender = function (nonFormattedValue) {
const { primaryCurrency, convertedCurrency, conversionRate } = this.props
if (conversionRate === 0 || conversionRate === null || conversionRate === undefined) {
if (nonFormattedValue !== 0) {
return null
}
}
let convertedValue = conversionUtil(nonFormattedValue, {
fromNumericBase: 'dec',
fromCurrency: primaryCurrency,
toCurrency: convertedCurrency,
numberOfDecimals: 2,
conversionRate,
})
convertedValue = Number(convertedValue).toFixed(2)
const upperCaseCurrencyCode = convertedCurrency.toUpperCase()
return currencies.find(currency => currency.code === upperCaseCurrencyCode)
? currencyFormatter.format(Number(convertedValue), {
code: upperCaseCurrencyCode,
})
: convertedValue
}
CurrencyDisplay.prototype.handleChange = function (newVal) {
this.setState({ valueToRender: removeLeadingZeroes(newVal) })
this.props.onChange(this.getAmount(newVal))
}
CurrencyDisplay.prototype.getInputWidth = function (valueToRender, readOnly) {
const valueString = String(valueToRender)
const valueLength = valueString.length || 1
const decimalPointDeficit = valueString.match(/\./) ? -0.5 : 0
return (valueLength + decimalPointDeficit + 0.75) + 'ch'
}
CurrencyDisplay.prototype.onlyRenderConversions = function (convertedValueToRender) {
const {
convertedBalanceClassName = 'currency-display__converted-value',
convertedCurrency,
} = this.props
return h('div', {
className: convertedBalanceClassName,
}, convertedValueToRender == null
? this.context.t('noConversionRateAvailable')
: `${convertedValueToRender} ${convertedCurrency.toUpperCase()}`
)
}
CurrencyDisplay.prototype.render = function () {
const {
className = 'currency-display',
primaryBalanceClassName = 'currency-display__input',
primaryCurrency,
readOnly = false,
inError = false,
onBlur,
step,
} = this.props
const { valueToRender } = this.state
const convertedValueToRender = this.getConvertedValueToRender(valueToRender)
return h('div', {
className,
style: {
borderColor: inError ? 'red' : null,
},
onClick: () => {
this.currencyInput && this.currencyInput.focus()
},
}, [
h('div.currency-display__primary-row', [
h('div.currency-display__input-wrapper', [
h('input', {
className: primaryBalanceClassName,
value: `${valueToRender}`,
placeholder: '0',
type: 'number',
readOnly,
...(!readOnly ? {
onChange: e => this.handleChange(e.target.value),
onBlur: () => onBlur(this.getAmount(valueToRender)),
} : {}),
ref: input => { this.currencyInput = input },
style: {
width: this.getInputWidth(valueToRender, readOnly),
},
min: 0,
step,
}),
h('span.currency-display__currency-symbol', primaryCurrency),
]),
]), this.onlyRenderConversions(convertedValueToRender),
])
}

@ -1 +0,0 @@
export { default } from './currency-display.js'

@ -1,91 +0,0 @@
import React from 'react'
import assert from 'assert'
import sinon from 'sinon'
import { shallow, mount } from 'enzyme'
import CurrencyDisplay from '../currency-display'
describe('', () => {
const token = {
address: '0xTest',
symbol: 'TST',
decimals: '13',
}
it('retuns ETH value for wei value', () => {
const wrapper = mount(<CurrencyDisplay />, {context: {t: str => str + '_t'}})
const value = wrapper.instance().getValueToRender({
// 1000000000000000000
value: 'DE0B6B3A7640000',
})
assert.equal(value, 1)
})
it('returns value of token based on token decimals', () => {
const wrapper = mount(<CurrencyDisplay />, {context: {t: str => str + '_t'}})
const value = wrapper.instance().getValueToRender({
selectedToken: token,
// 1000000000000000000
value: 'DE0B6B3A7640000',
})
assert.equal(value, 100000)
})
it('returns hex value with decimal adjustment', () => {
const wrapper = mount(
<CurrencyDisplay
selectedToken={token}
/>, {context: {t: str => str + '_t'}})
const value = wrapper.instance().getAmount(1)
// 10000000000000
assert.equal(value, '9184e72a000')
})
it('#getConvertedValueToRender converts input value based on conversionRate', () => {
const wrapper = mount(
<CurrencyDisplay
primaryCurrency={'usd'}
convertedCurrency={'ja'}
conversionRate={2}
/>, {context: {t: str => str + '_t'}})
const value = wrapper.instance().getConvertedValueToRender(32)
assert.equal(value, 64)
})
it('#onlyRenderConversions renders single element for converted currency and value', () => {
const wrapper = mount(
<CurrencyDisplay
convertedCurrency={'test'}
/>, {context: {t: str => str + '_t'}})
const value = wrapper.instance().onlyRenderConversions(10)
assert.equal(value.props.className, 'currency-display__converted-value')
assert.equal(value.props.children, '10 TEST')
})
it('simulates change value in input', () => {
const handleChangeSpy = sinon.spy()
const wrapper = shallow(
<CurrencyDisplay
onChange={handleChangeSpy}
/>, {context: {t: str => str + '_t'}})
const input = wrapper.find('input')
input.simulate('focus')
input.simulate('change', { target: { value: '100' } })
assert.equal(wrapper.state().valueToRender, '100')
assert.equal(wrapper.find('input').prop('value'), '100')
})
})

@ -34,19 +34,25 @@ export default class AmountMaxButton extends Component {
})
}
render () {
const { setMaxModeTo, maxModeOn } = this.props
onMaxClick = (event) => {
const { setMaxModeTo } = this.props
return (
<div
className="send-v2__amount-max"
onClick={(event) => {
event.preventDefault()
setMaxModeTo(true)
this.setMaxAmount()
}}
}
render () {
return this.props.maxModeOn
? null
: (
<div>
<span
className="send-v2__amount-max"
onClick={this.onMaxClick}
>
{!maxModeOn ? this.context.t('max') : ''}
{this.context.t('max')}
</span>
</div>
)
}

@ -56,9 +56,8 @@ describe('AmountMaxButton Component', function () {
})
describe('render', () => {
it('should render a div with a send-v2__amount-max class', () => {
assert.equal(wrapper.find('.send-v2__amount-max').length, 1)
assert(wrapper.find('.send-v2__amount-max').is('div'))
it('should render an element with a send-v2__amount-max class', () => {
assert(wrapper.exists('.send-v2__amount-max'))
})
it('should call setMaxModeTo and setMaxAmount when the send-v2__amount-max div is clicked', () => {
@ -77,9 +76,9 @@ describe('AmountMaxButton Component', function () {
)
})
it('should not render text when maxModeOn is true', () => {
it('should not render anything when maxModeOn is true', () => {
wrapper.setProps({ maxModeOn: true })
assert.equal(wrapper.find('.send-v2__amount-max').text(), '')
assert.ok(!wrapper.exists('.send-v2__amount-max'))
})
it('should render the expected text when maxModeOn is false', () => {

@ -2,7 +2,8 @@ import React, { Component } from 'react'
import PropTypes from 'prop-types'
import SendRowWrapper from '../send-row-wrapper/'
import AmountMaxButton from './amount-max-button/'
import CurrencyDisplay from '../../currency-display'
import UserPreferencedCurrencyInput from '../../../user-preferenced-currency-input'
import UserPreferencedTokenInput from '../../../user-preferenced-token-input'
export default class SendAmountRow extends Component {
@ -84,16 +85,25 @@ export default class SendAmountRow extends Component {
}
}
renderInput () {
const { amount, inError, selectedToken } = this.props
const Component = selectedToken ? UserPreferencedTokenInput : UserPreferencedCurrencyInput
return (
<Component
onChange={newAmount => this.validateAmount(newAmount)}
onBlur={newAmount => {
this.updateGas(newAmount)
this.updateAmount(newAmount)
}}
error={inError}
value={amount}
/>
)
}
render () {
const {
amount,
amountConversionRate,
convertedCurrency,
gasTotal,
inError,
primaryCurrency,
selectedToken,
} = this.props
const { gasTotal, inError } = this.props
return (
<SendRowWrapper
@ -102,20 +112,7 @@ export default class SendAmountRow extends Component {
errorType={'amount'}
>
{!inError && gasTotal && <AmountMaxButton />}
<CurrencyDisplay
conversionRate={amountConversionRate}
convertedCurrency={convertedCurrency}
onBlur={newAmount => {
this.updateGas(newAmount)
this.updateAmount(newAmount)
}}
onChange={newAmount => this.validateAmount(newAmount)}
inError={inError}
primaryCurrency={primaryCurrency || 'ETH'}
selectedToken={selectedToken}
value={amount}
step="any"
/>
{ this.renderInput() }
</SendRowWrapper>
)
}

@ -6,7 +6,7 @@ import SendAmountRow from '../send-amount-row.component.js'
import SendRowWrapper from '../../send-row-wrapper/send-row-wrapper.component'
import AmountMaxButton from '../amount-max-button/amount-max-button.container'
import CurrencyDisplay from '../../../currency-display'
import UserPreferencedTokenInput from '../../../../user-preferenced-token-input'
const propsMethodSpies = {
setMaxModeTo: sinon.spy(),
@ -150,26 +150,19 @@ describe('SendAmountRow Component', function () {
assert(wrapper.find(SendRowWrapper).childAt(0).is(AmountMaxButton))
})
it('should render a CurrencyDisplay as the second child of the SendRowWrapper', () => {
assert(wrapper.find(SendRowWrapper).childAt(1).is(CurrencyDisplay))
it('should render a UserPreferencedTokenInput as the second child of the SendRowWrapper', () => {
console.log('HI', wrapper.find(SendRowWrapper).childAt(1))
assert(wrapper.find(SendRowWrapper).childAt(1).is(UserPreferencedTokenInput))
})
it('should render the CurrencyDisplay with the correct props', () => {
it('should render the UserPreferencedTokenInput with the correct props', () => {
const {
conversionRate,
convertedCurrency,
onBlur,
onChange,
inError,
primaryCurrency,
selectedToken,
error,
value,
} = wrapper.find(SendRowWrapper).childAt(1).props()
assert.equal(conversionRate, 'mockAmountConversionRate')
assert.equal(convertedCurrency, 'mockConvertedCurrency')
assert.equal(inError, false)
assert.equal(primaryCurrency, 'mockPrimaryCurrency')
assert.deepEqual(selectedToken, { address: 'mockTokenAddress' })
assert.equal(error, false)
assert.equal(value, 'mockAmount')
assert.equal(SendAmountRow.prototype.updateGas.callCount, 0)
assert.equal(SendAmountRow.prototype.updateAmount.callCount, 0)
@ -192,11 +185,5 @@ describe('SendAmountRow Component', function () {
['mockNewAmount']
)
})
it('should pass the default primaryCurrency to the CurrencyDisplay if primaryCurrency is falsy', () => {
wrapper.setProps({ primaryCurrency: null })
const { primaryCurrency } = wrapper.find(SendRowWrapper).childAt(1).props()
assert.equal(primaryCurrency, 'ETH')
})
})
})

@ -1,7 +1,7 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import CurrencyDisplay from '../../../../send/currency-display'
import UserPreferencedCurrencyDisplay from '../../../../user-preferenced-currency-display'
import { PRIMARY, SECONDARY } from '../../../../../constants/common'
export default class GasFeeDisplay extends Component {
@ -19,27 +19,24 @@ export default class GasFeeDisplay extends Component {
};
render () {
const {
conversionRate,
gasTotal,
onClick,
primaryCurrency = 'ETH',
convertedCurrency,
gasLoadingError,
} = this.props
const { gasTotal, onClick, gasLoadingError } = this.props
return (
<div className="send-v2__gas-fee-display">
{gasTotal
? <CurrencyDisplay
primaryCurrency={primaryCurrency}
convertedCurrency={convertedCurrency}
? (
<div className="currency-display">
<UserPreferencedCurrencyDisplay
value={gasTotal}
type={PRIMARY}
/>
<UserPreferencedCurrencyDisplay
className="currency-display__converted-value"
value={gasTotal}
conversionRate={conversionRate}
gasLoadingError={gasLoadingError}
convertedPrefix={'$'}
readOnly
type={SECONDARY}
/>
</div>
)
: gasLoadingError
? <div className="currency-display.currency-display--message">
{this.context.t('setGasPrice')}

@ -2,7 +2,7 @@ import React from 'react'
import assert from 'assert'
import {shallow} from 'enzyme'
import GasFeeDisplay from '../gas-fee-display.component'
import CurrencyDisplay from '../../../../../send/currency-display'
import UserPreferencedCurrencyDisplay from '../../../../../user-preferenced-currency-display'
import sinon from 'sinon'
@ -29,17 +29,15 @@ describe('SendGasRow Component', function () {
describe('render', () => {
it('should render a CurrencyDisplay component', () => {
assert.equal(wrapper.find(CurrencyDisplay).length, 1)
assert.equal(wrapper.find(UserPreferencedCurrencyDisplay).length, 2)
})
it('should render the CurrencyDisplay with the correct props', () => {
const {
conversionRate,
convertedCurrency,
type,
value,
} = wrapper.find(CurrencyDisplay).props()
assert.equal(conversionRate, 20)
assert.equal(convertedCurrency, 'mockConvertedCurrency')
} = wrapper.find(UserPreferencedCurrencyDisplay).at(0).props()
assert.equal(type, 'PRIMARY')
assert.equal(value, 'mockGasTotal')
})

@ -0,0 +1 @@
export { default } from './token-input.container'

@ -0,0 +1,308 @@
import React from 'react'
import PropTypes from 'prop-types'
import assert from 'assert'
import { shallow, mount } from 'enzyme'
import sinon from 'sinon'
import { Provider } from 'react-redux'
import configureMockStore from 'redux-mock-store'
import TokenInput from '../token-input.component'
import UnitInput from '../../unit-input'
import CurrencyDisplay from '../../currency-display'
describe('TokenInput Component', () => {
const t = key => `translate ${key}`
describe('rendering', () => {
it('should render properly without a token', () => {
const wrapper = shallow(
<TokenInput />,
{ context: { t } }
)
assert.ok(wrapper)
assert.equal(wrapper.find(UnitInput).length, 1)
})
it('should render properly with a token', () => {
const mockStore = {
metamask: {
currentCurrency: 'usd',
conversionRate: 231.06,
},
}
const store = configureMockStore()(mockStore)
const wrapper = mount(
<Provider store={store}>
<TokenInput
selectedToken={{
address: '0x1',
decimals: '4',
symbol: 'ABC',
}}
suffix="ABC"
/>
</Provider>,
{ context: { t },
childContextTypes: {
t: PropTypes.func,
},
},
)
assert.ok(wrapper)
assert.equal(wrapper.find('.unit-input__suffix').length, 1)
assert.equal(wrapper.find('.unit-input__suffix').text(), 'ABC')
assert.equal(wrapper.find('.currency-input__conversion-component').length, 1)
assert.equal(wrapper.find('.currency-input__conversion-component').text(), 'translate noConversionRateAvailable')
})
it('should render properly with a token and selectedTokenExchangeRate', () => {
const mockStore = {
metamask: {
currentCurrency: 'usd',
conversionRate: 231.06,
},
}
const store = configureMockStore()(mockStore)
const wrapper = mount(
<Provider store={store}>
<TokenInput
selectedToken={{
address: '0x1',
decimals: '4',
symbol: 'ABC',
}}
suffix="ABC"
selectedTokenExchangeRate={2}
/>
</Provider>,
{ context: { t },
childContextTypes: {
t: PropTypes.func,
},
},
)
assert.ok(wrapper)
assert.equal(wrapper.find('.unit-input__suffix').length, 1)
assert.equal(wrapper.find('.unit-input__suffix').text(), 'ABC')
assert.equal(wrapper.find(CurrencyDisplay).length, 1)
})
it('should render properly with a token value for ETH', () => {
const mockStore = {
metamask: {
currentCurrency: 'usd',
conversionRate: 231.06,
},
}
const store = configureMockStore()(mockStore)
const wrapper = mount(
<Provider store={store}>
<TokenInput
value="2710"
selectedToken={{
address: '0x1',
decimals: '4',
symbol: 'ABC',
}}
suffix="ABC"
selectedTokenExchangeRate={2}
/>
</Provider>
)
assert.ok(wrapper)
const tokenInputInstance = wrapper.find(TokenInput).at(0).instance()
assert.equal(tokenInputInstance.state.decimalValue, 1)
assert.equal(tokenInputInstance.state.hexValue, '2710')
assert.equal(wrapper.find('.unit-input__suffix').length, 1)
assert.equal(wrapper.find('.unit-input__suffix').text(), 'ABC')
assert.equal(wrapper.find('.unit-input__input').props().value, '1')
assert.equal(wrapper.find('.currency-display-component').text(), '2 ETH')
})
it('should render properly with a token value for fiat', () => {
const mockStore = {
metamask: {
currentCurrency: 'usd',
conversionRate: 231.06,
},
}
const store = configureMockStore()(mockStore)
const wrapper = mount(
<Provider store={store}>
<TokenInput
value="2710"
selectedToken={{
address: '0x1',
decimals: '4',
symbol: 'ABC',
}}
suffix="ABC"
selectedTokenExchangeRate={2}
showFiat
/>
</Provider>
)
assert.ok(wrapper)
const tokenInputInstance = wrapper.find(TokenInput).at(0).instance()
assert.equal(tokenInputInstance.state.decimalValue, 1)
assert.equal(tokenInputInstance.state.hexValue, '2710')
assert.equal(wrapper.find('.unit-input__suffix').length, 1)
assert.equal(wrapper.find('.unit-input__suffix').text(), 'ABC')
assert.equal(wrapper.find('.unit-input__input').props().value, '1')
assert.equal(wrapper.find('.currency-display-component').text(), '$462.12 USD')
})
})
describe('handling actions', () => {
const handleChangeSpy = sinon.spy()
const handleBlurSpy = sinon.spy()
afterEach(() => {
handleChangeSpy.resetHistory()
handleBlurSpy.resetHistory()
})
it('should call onChange and onBlur on input changes with the hex value for ETH', () => {
const mockStore = {
metamask: {
currentCurrency: 'usd',
conversionRate: 231.06,
},
}
const store = configureMockStore()(mockStore)
const wrapper = mount(
<Provider store={store}>
<TokenInput
onChange={handleChangeSpy}
onBlur={handleBlurSpy}
selectedToken={{
address: '0x1',
decimals: '4',
symbol: 'ABC',
}}
suffix="ABC"
selectedTokenExchangeRate={2}
/>
</Provider>
)
assert.ok(wrapper)
assert.equal(handleChangeSpy.callCount, 0)
assert.equal(handleBlurSpy.callCount, 0)
const tokenInputInstance = wrapper.find(TokenInput).at(0).instance()
assert.equal(tokenInputInstance.state.decimalValue, 0)
assert.equal(tokenInputInstance.state.hexValue, undefined)
assert.equal(wrapper.find('.currency-display-component').text(), '0 ETH')
const input = wrapper.find('input')
assert.equal(input.props().value, 0)
input.simulate('change', { target: { value: 1 } })
assert.equal(handleChangeSpy.callCount, 1)
assert.ok(handleChangeSpy.calledWith('2710'))
assert.equal(wrapper.find('.currency-display-component').text(), '2 ETH')
assert.equal(tokenInputInstance.state.decimalValue, 1)
assert.equal(tokenInputInstance.state.hexValue, '2710')
assert.equal(handleBlurSpy.callCount, 0)
input.simulate('blur')
assert.equal(handleBlurSpy.callCount, 1)
assert.ok(handleBlurSpy.calledWith('2710'))
})
it('should call onChange and onBlur on input changes with the hex value for fiat', () => {
const mockStore = {
metamask: {
currentCurrency: 'usd',
conversionRate: 231.06,
},
}
const store = configureMockStore()(mockStore)
const wrapper = mount(
<Provider store={store}>
<TokenInput
onChange={handleChangeSpy}
onBlur={handleBlurSpy}
selectedToken={{
address: '0x1',
decimals: '4',
symbol: 'ABC',
}}
suffix="ABC"
selectedTokenExchangeRate={2}
showFiat
/>
</Provider>
)
assert.ok(wrapper)
assert.equal(handleChangeSpy.callCount, 0)
assert.equal(handleBlurSpy.callCount, 0)
const tokenInputInstance = wrapper.find(TokenInput).at(0).instance()
assert.equal(tokenInputInstance.state.decimalValue, 0)
assert.equal(tokenInputInstance.state.hexValue, undefined)
assert.equal(wrapper.find('.currency-display-component').text(), '$0.00 USD')
const input = wrapper.find('input')
assert.equal(input.props().value, 0)
input.simulate('change', { target: { value: 1 } })
assert.equal(handleChangeSpy.callCount, 1)
assert.ok(handleChangeSpy.calledWith('2710'))
assert.equal(wrapper.find('.currency-display-component').text(), '$462.12 USD')
assert.equal(tokenInputInstance.state.decimalValue, 1)
assert.equal(tokenInputInstance.state.hexValue, '2710')
assert.equal(handleBlurSpy.callCount, 0)
input.simulate('blur')
assert.equal(handleBlurSpy.callCount, 1)
assert.ok(handleBlurSpy.calledWith('2710'))
})
it('should change the state and pass in a new decimalValue when props.value changes', () => {
const mockStore = {
metamask: {
currentCurrency: 'usd',
conversionRate: 231.06,
},
}
const store = configureMockStore()(mockStore)
const wrapper = shallow(
<Provider store={store}>
<TokenInput
onChange={handleChangeSpy}
onBlur={handleBlurSpy}
selectedToken={{
address: '0x1',
decimals: '4',
symbol: 'ABC',
}}
suffix="ABC"
selectedTokenExchangeRate={2}
showFiat
/>
</Provider>
)
assert.ok(wrapper)
const tokenInputInstance = wrapper.find(TokenInput).dive()
assert.equal(tokenInputInstance.state('decimalValue'), 0)
assert.equal(tokenInputInstance.state('hexValue'), undefined)
assert.equal(tokenInputInstance.find(UnitInput).props().value, 0)
tokenInputInstance.setProps({ value: '2710' })
tokenInputInstance.update()
assert.equal(tokenInputInstance.state('decimalValue'), 1)
assert.equal(tokenInputInstance.state('hexValue'), '2710')
assert.equal(tokenInputInstance.find(UnitInput).props().value, 1)
})
})
})

@ -0,0 +1,129 @@
import assert from 'assert'
import proxyquire from 'proxyquire'
let mapStateToProps, mergeProps
proxyquire('../token-input.container.js', {
'react-redux': {
connect: (ms, md, mp) => {
mapStateToProps = ms
mergeProps = mp
return () => ({})
},
},
})
describe('TokenInput container', () => {
describe('mapStateToProps()', () => {
it('should return the correct props when send is empty', () => {
const mockState = {
metamask: {
currentCurrency: 'usd',
tokens: [
{
address: '0x1',
decimals: '4',
symbol: 'ABC',
},
],
selectedTokenAddress: '0x1',
contractExchangeRates: {},
send: {},
},
}
assert.deepEqual(mapStateToProps(mockState), {
currentCurrency: 'usd',
selectedToken: {
address: '0x1',
decimals: '4',
symbol: 'ABC',
},
selectedTokenExchangeRate: 0,
})
})
it('should return the correct props when selectedTokenAddress is not found and send is populated', () => {
const mockState = {
metamask: {
currentCurrency: 'usd',
tokens: [
{
address: '0x1',
decimals: '4',
symbol: 'ABC',
},
],
selectedTokenAddress: '0x2',
contractExchangeRates: {},
send: {
token: { address: 'test' },
},
},
}
assert.deepEqual(mapStateToProps(mockState), {
currentCurrency: 'usd',
selectedToken: {
address: 'test',
},
selectedTokenExchangeRate: 0,
})
})
it('should return the correct props when contractExchangeRates is populated', () => {
const mockState = {
metamask: {
currentCurrency: 'usd',
tokens: [
{
address: '0x1',
decimals: '4',
symbol: 'ABC',
},
],
selectedTokenAddress: '0x1',
contractExchangeRates: {
'0x1': 5,
},
send: {},
},
}
assert.deepEqual(mapStateToProps(mockState), {
currentCurrency: 'usd',
selectedToken: {
address: '0x1',
decimals: '4',
symbol: 'ABC',
},
selectedTokenExchangeRate: 5,
})
})
})
describe('mergeProps()', () => {
it('should return the correct props', () => {
const mockStateProps = {
currentCurrency: 'usd',
selectedToken: {
address: '0x1',
decimals: '4',
symbol: 'ABC',
},
selectedTokenExchangeRate: 5,
}
assert.deepEqual(mergeProps(mockStateProps, {}, {}), {
currentCurrency: 'usd',
selectedToken: {
address: '0x1',
decimals: '4',
symbol: 'ABC',
},
selectedTokenExchangeRate: 5,
suffix: 'ABC',
})
})
})
})

@ -0,0 +1,136 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import UnitInput from '../unit-input'
import CurrencyDisplay from '../currency-display'
import { getWeiHexFromDecimalValue } from '../../helpers/conversions.util'
import ethUtil from 'ethereumjs-util'
import { conversionUtil, multiplyCurrencies } from '../../conversion-util'
import { ETH } from '../../constants/common'
/**
* Component that allows user to enter token values as a number, and props receive a converted
* hex value. props.value, used as a default or forced value, should be a hex value, which
* gets converted into a decimal value.
*/
export default class TokenInput extends PureComponent {
static contextTypes = {
t: PropTypes.func,
}
static propTypes = {
currentCurrency: PropTypes.string,
onChange: PropTypes.func,
onBlur: PropTypes.func,
value: PropTypes.string,
suffix: PropTypes.string,
showFiat: PropTypes.bool,
selectedToken: PropTypes.object,
selectedTokenExchangeRate: PropTypes.number,
}
constructor (props) {
super(props)
const { value: hexValue } = props
const decimalValue = hexValue ? this.getDecimalValue(props) : 0
this.state = {
decimalValue,
hexValue,
}
}
componentDidUpdate (prevProps) {
const { value: prevPropsHexValue } = prevProps
const { value: propsHexValue } = this.props
const { hexValue: stateHexValue } = this.state
if (prevPropsHexValue !== propsHexValue && propsHexValue !== stateHexValue) {
const decimalValue = this.getDecimalValue(this.props)
this.setState({ hexValue: propsHexValue, decimalValue })
}
}
getDecimalValue (props) {
const { value: hexValue, selectedToken: { decimals, symbol } = {} } = props
const multiplier = Math.pow(10, Number(decimals || 0))
const decimalValueString = conversionUtil(ethUtil.addHexPrefix(hexValue), {
fromNumericBase: 'hex',
toNumericBase: 'dec',
toCurrency: symbol,
conversionRate: multiplier,
invertConversionRate: true,
})
return Number(decimalValueString) || 0
}
handleChange = decimalValue => {
const { selectedToken: { decimals } = {}, onChange } = this.props
const multiplier = Math.pow(10, Number(decimals || 0))
const hexValue = multiplyCurrencies(decimalValue || 0, multiplier, { toNumericBase: 'hex' })
this.setState({ hexValue, decimalValue })
onChange(hexValue)
}
handleBlur = () => {
this.props.onBlur(this.state.hexValue)
}
renderConversionComponent () {
const { selectedTokenExchangeRate, showFiat, currentCurrency } = this.props
const { decimalValue } = this.state
let currency, numberOfDecimals
if (showFiat) {
// Display Fiat
currency = currentCurrency
numberOfDecimals = 2
} else {
// Display ETH
currency = ETH
numberOfDecimals = 6
}
const decimalEthValue = (decimalValue * selectedTokenExchangeRate) || 0
const hexWeiValue = getWeiHexFromDecimalValue({
value: decimalEthValue,
fromCurrency: ETH,
fromDenomination: ETH,
})
return selectedTokenExchangeRate
? (
<CurrencyDisplay
className="currency-input__conversion-component"
currency={currency}
value={hexWeiValue}
numberOfDecimals={numberOfDecimals}
/>
) : (
<div className="currency-input__conversion-component">
{ this.context.t('noConversionRateAvailable') }
</div>
)
}
render () {
const { suffix, ...restProps } = this.props
const { decimalValue } = this.state
return (
<UnitInput
{...restProps}
suffix={suffix}
onChange={this.handleChange}
onBlur={this.handleBlur}
value={decimalValue}
>
{ this.renderConversionComponent() }
</UnitInput>
)
}
}

@ -0,0 +1,27 @@
import { connect } from 'react-redux'
import TokenInput from './token-input.component'
import { getSelectedToken, getSelectedTokenExchangeRate } from '../../selectors'
const mapStateToProps = state => {
const { metamask: { currentCurrency } } = state
return {
currentCurrency,
selectedToken: getSelectedToken(state),
selectedTokenExchangeRate: getSelectedTokenExchangeRate(state),
}
}
const mergeProps = (stateProps, dispatchProps, ownProps) => {
const { selectedToken } = stateProps
const suffix = selectedToken && selectedToken.symbol
return {
...stateProps,
...dispatchProps,
...ownProps,
suffix,
}
}
export default connect(mapStateToProps, null, mergeProps)(TokenInput)

@ -46,11 +46,15 @@ export function getActivities (transaction) {
if (!Array.isArray(base) && base.status === UNAPPROVED_STATUS && base.txParams) {
const { time, txParams: { value } = {} } = base
return acc.concat(eventCreator(TRANSACTION_CREATED_EVENT, time, value))
// An entry in the history may be an array of more sub-entries.
} else if (Array.isArray(base)) {
const events = []
base.forEach(entry => {
const { op, path, value, timestamp } = entry
const { op, path, value, timestamp: entryTimestamp } = entry
// Not all sub-entries in a history entry have a timestamp. If the sub-entry does not have a
// timestamp, the first sub-entry in a history entry should.
const timestamp = entryTimestamp || base[0] && base[0].timestamp
if (path in eventPathsHash && op === REPLACE_OP) {
switch (path) {

@ -4,8 +4,9 @@ import classnames from 'classnames'
import TransactionBreakdownRow from './transaction-breakdown-row'
import Card from '../card'
import CurrencyDisplay from '../currency-display'
import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'
import HexToDecimal from '../hex-to-decimal'
import { ETH, GWEI } from '../../constants/common'
import { ETH, GWEI, PRIMARY, SECONDARY } from '../../constants/common'
import { getHexGasTotal } from '../../helpers/confirm-transaction/util'
import { sumHexes } from '../../helpers/transactions.util'
@ -26,8 +27,11 @@ export default class TransactionBreakdown extends PureComponent {
render () {
const { t } = this.context
const { transaction, className } = this.props
const { txParams: { gas, gasPrice, value } = {} } = transaction
const hexGasTotal = getHexGasTotal({ gasLimit: gas, gasPrice })
const { txParams: { gas, gasPrice, value } = {}, txReceipt: { gasUsed } = {} } = transaction
const gasLimit = typeof gasUsed === 'string' ? gasUsed : gas
const hexGasTotal = getHexGasTotal({ gasLimit, gasPrice })
const totalInHex = sumHexes(hexGasTotal, value)
return (
@ -37,9 +41,9 @@ export default class TransactionBreakdown extends PureComponent {
className="transaction-breakdown__card"
>
<TransactionBreakdownRow title={t('amount')}>
<CurrencyDisplay
<UserPreferencedCurrencyDisplay
className="transaction-breakdown__value"
currency={ETH}
type={PRIMARY}
value={value}
/>
</TransactionBreakdownRow>
@ -52,6 +56,19 @@ export default class TransactionBreakdown extends PureComponent {
value={gas}
/>
</TransactionBreakdownRow>
{
typeof gasUsed === 'string' && (
<TransactionBreakdownRow
title={`${t('gasUsed')} (${t('units')})`}
className="transaction-breakdown__row-title"
>
<HexToDecimal
className="transaction-breakdown__value"
value={gasUsed}
/>
</TransactionBreakdownRow>
)
}
<TransactionBreakdownRow title={t('gasPrice')}>
<CurrencyDisplay
className="transaction-breakdown__value"
@ -63,14 +80,14 @@ export default class TransactionBreakdown extends PureComponent {
</TransactionBreakdownRow>
<TransactionBreakdownRow title={t('total')}>
<div>
<CurrencyDisplay
<UserPreferencedCurrencyDisplay
className="transaction-breakdown__value transaction-breakdown__value--eth-total"
currency={ETH}
type={PRIMARY}
value={totalInHex}
numberOfDecimals={6}
/>
<CurrencyDisplay
<UserPreferencedCurrencyDisplay
className="transaction-breakdown__value"
type={SECONDARY}
value={totalInHex}
/>
</div>

@ -5,6 +5,7 @@ import { CARDS_VARIANT } from '../sender-to-recipient/sender-to-recipient.consta
import TransactionActivityLog from '../transaction-activity-log'
import TransactionBreakdown from '../transaction-breakdown'
import Button from '../button'
import Tooltip from '../tooltip'
import prefixForNetwork from '../../../lib/etherscan-prefix-for-network'
export default class TransactionListItemDetails extends PureComponent {
@ -75,6 +76,7 @@ export default class TransactionListItemDetails extends PureComponent {
</Button>
)
}
<Tooltip title={t('viewOnEtherscan')}>
<Button
type="raised"
onClick={this.handleEtherscanClick}
@ -82,6 +84,7 @@ export default class TransactionListItemDetails extends PureComponent {
>
<img src="/images/arrow-popout.svg" />
</Button>
</Tooltip>
</div>
</div>
<div className="transaction-list-item-details__sender-to-recipient-container">

@ -4,13 +4,14 @@ import classnames from 'classnames'
import Identicon from '../identicon'
import TransactionStatus from '../transaction-status'
import TransactionAction from '../transaction-action'
import CurrencyDisplay from '../currency-display'
import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'
import TokenCurrencyDisplay from '../token-currency-display'
import TransactionListItemDetails from '../transaction-list-item-details'
import { CONFIRM_TRANSACTION_ROUTE } from '../../routes'
import { UNAPPROVED_STATUS, TOKEN_METHOD_TRANSFER } from '../../constants/transactions'
import { ETH } from '../../constants/common'
import { PRIMARY, SECONDARY } from '../../constants/common'
import { ENVIRONMENT_TYPE_FULLSCREEN } from '../../../../app/scripts/lib/enums'
import { getStatusKey } from '../../helpers/transactions.util'
export default class TransactionListItem extends PureComponent {
static propTypes = {
@ -102,12 +103,11 @@ export default class TransactionListItem extends PureComponent {
prefix="-"
/>
) : (
<CurrencyDisplay
<UserPreferencedCurrencyDisplay
className="transaction-list-item__amount transaction-list-item__amount--primary"
value={value}
type={PRIMARY}
prefix="-"
numberOfDecimals={2}
currency={ETH}
/>
)
}
@ -118,10 +118,11 @@ export default class TransactionListItem extends PureComponent {
return token
? null
: (
<CurrencyDisplay
<UserPreferencedCurrencyDisplay
className="transaction-list-item__amount transaction-list-item__amount--secondary"
prefix="-"
value={value}
prefix="-"
type={SECONDARY}
/>
)
}
@ -167,7 +168,7 @@ export default class TransactionListItem extends PureComponent {
</div>
<TransactionStatus
className="transaction-list-item__status"
statusKey={transaction.status}
statusKey={getStatusKey(transaction)}
title={(
(transaction.err && transaction.err.rpc)
? transaction.err.rpc.message

@ -25,4 +25,9 @@
background-color: #FFF2DB;
color: #CA810A;
}
&--failed {
background: lighten($monzo, 56%);
color: $monzo;
}
}

@ -3,7 +3,7 @@ import assert from 'assert'
import { shallow } from 'enzyme'
import sinon from 'sinon'
import TokenBalance from '../../token-balance'
import CurrencyDisplay from '../../currency-display'
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'
import { SEND_ROUTE } from '../../../routes'
import TransactionViewBalance from '../transaction-view-balance.component'
@ -35,7 +35,7 @@ describe('TransactionViewBalance Component', () => {
assert.equal(wrapper.find('.transaction-view-balance').length, 1)
assert.equal(wrapper.find('.transaction-view-balance__button').length, 2)
assert.equal(wrapper.find(CurrencyDisplay).length, 2)
assert.equal(wrapper.find(UserPreferencedCurrencyDisplay).length, 2)
const buttons = wrapper.find('.transaction-view-balance__buttons')
assert.equal(propsMethodSpies.showDepositModal.callCount, 0)

@ -3,9 +3,9 @@ import PropTypes from 'prop-types'
import Button from '../button'
import Identicon from '../identicon'
import TokenBalance from '../token-balance'
import CurrencyDisplay from '../currency-display'
import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'
import { SEND_ROUTE } from '../../routes'
import { ETH } from '../../constants/common'
import { PRIMARY, SECONDARY } from '../../constants/common'
export default class TransactionViewBalance extends PureComponent {
static contextTypes = {
@ -33,15 +33,17 @@ export default class TransactionViewBalance extends PureComponent {
/>
) : (
<div className="transaction-view-balance__balance">
<CurrencyDisplay
<UserPreferencedCurrencyDisplay
className="transaction-view-balance__primary-balance"
value={balance}
currency={ETH}
numberOfDecimals={3}
type={PRIMARY}
ethNumberOfDecimals={3}
/>
<CurrencyDisplay
<UserPreferencedCurrencyDisplay
className="transaction-view-balance__secondary-balance"
value={balance}
type={SECONDARY}
ethNumberOfDecimals={3}
/>
</div>
)

@ -0,0 +1 @@
export { default } from './unit-input.component'

@ -0,0 +1,44 @@
.unit-input {
min-height: 54px;
border: 1px solid #dedede;
border-radius: 4px;
background-color: #fff;
color: #4d4d4d;
font-size: 1rem;
padding: 8px 10px;
position: relative;
input[type="number"] {
-moz-appearance: textfield;
}
input[type="number"]::-webkit-inner-spin-button {
-webkit-appearance: none;
-moz-appearance: none;
display: none;
}
input[type="number"]:hover::-webkit-inner-spin-button {
-webkit-appearance: none;
-moz-appearance: none;
display: none;
}
&__input {
color: #4d4d4d;
font-size: 1rem;
font-family: Roboto;
border: none;
outline: 0 !important;
max-width: 22ch;
}
&__input-container {
display: flex;
align-items: center;
}
&--error {
border-color: $red;
}
}

@ -0,0 +1,146 @@
import React from 'react'
import assert from 'assert'
import { shallow, mount } from 'enzyme'
import sinon from 'sinon'
import UnitInput from '../unit-input.component'
describe('UnitInput Component', () => {
describe('rendering', () => {
it('should render properly without a suffix', () => {
const wrapper = shallow(
<UnitInput />
)
assert.ok(wrapper)
assert.equal(wrapper.find('.unit-input__suffix').length, 0)
})
it('should render properly with a suffix', () => {
const wrapper = shallow(
<UnitInput
suffix="ETH"
/>
)
assert.ok(wrapper)
assert.equal(wrapper.find('.unit-input__suffix').length, 1)
assert.equal(wrapper.find('.unit-input__suffix').text(), 'ETH')
})
it('should render properly with a child omponent', () => {
const wrapper = shallow(
<UnitInput>
<div className="testing">
TESTCOMPONENT
</div>
</UnitInput>
)
assert.ok(wrapper)
assert.equal(wrapper.find('.testing').length, 1)
assert.equal(wrapper.find('.testing').text(), 'TESTCOMPONENT')
})
it('should render with an error class when props.error === true', () => {
const wrapper = shallow(
<UnitInput
error
/>
)
assert.ok(wrapper)
assert.equal(wrapper.find('.unit-input--error').length, 1)
})
})
describe('handling actions', () => {
const handleChangeSpy = sinon.spy()
const handleBlurSpy = sinon.spy()
afterEach(() => {
handleChangeSpy.resetHistory()
handleBlurSpy.resetHistory()
})
it('should focus the input on component click', () => {
const wrapper = mount(
<UnitInput />
)
assert.ok(wrapper)
const handleFocusSpy = sinon.spy(wrapper.instance(), 'handleFocus')
wrapper.instance().forceUpdate()
wrapper.update()
assert.equal(handleFocusSpy.callCount, 0)
wrapper.find('.unit-input').simulate('click')
assert.equal(handleFocusSpy.callCount, 1)
})
it('should call onChange on input changes with the value', () => {
const wrapper = mount(
<UnitInput
onChange={handleChangeSpy}
/>
)
assert.ok(wrapper)
assert.equal(handleChangeSpy.callCount, 0)
const input = wrapper.find('input')
input.simulate('change', { target: { value: 123 } })
assert.equal(handleChangeSpy.callCount, 1)
assert.ok(handleChangeSpy.calledWith(123))
assert.equal(wrapper.state('value'), 123)
})
it('should call onBlur on blur with the value', () => {
const wrapper = mount(
<UnitInput
onChange={handleChangeSpy}
onBlur={handleBlurSpy}
/>
)
assert.ok(wrapper)
assert.equal(handleChangeSpy.callCount, 0)
assert.equal(handleBlurSpy.callCount, 0)
const input = wrapper.find('input')
input.simulate('change', { target: { value: 123 } })
assert.equal(handleChangeSpy.callCount, 1)
assert.ok(handleChangeSpy.calledWith(123))
assert.equal(wrapper.state('value'), 123)
input.simulate('blur')
assert.equal(handleBlurSpy.callCount, 1)
assert.ok(handleBlurSpy.calledWith(123))
})
it('should set the component state value with props.value', () => {
const wrapper = mount(
<UnitInput
value={123}
/>
)
assert.ok(wrapper)
assert.equal(wrapper.state('value'), 123)
})
it('should update the component state value with props.value', () => {
const wrapper = mount(
<UnitInput
onChange={handleChangeSpy}
/>
)
assert.ok(wrapper)
assert.equal(handleChangeSpy.callCount, 0)
const input = wrapper.find('input')
input.simulate('change', { target: { value: 123 } })
assert.equal(wrapper.state('value'), 123)
assert.equal(handleChangeSpy.callCount, 1)
assert.ok(handleChangeSpy.calledWith(123))
wrapper.setProps({ value: 456 })
assert.equal(wrapper.state('value'), 456)
assert.equal(handleChangeSpy.callCount, 1)
})
})
})

@ -0,0 +1,104 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import { removeLeadingZeroes } from '../send/send.utils'
/**
* Component that attaches a suffix or unit of measurement trailing user input, ex. 'ETH'. Also
* allows rendering a child component underneath the input to, for example, display conversions of
* the shown suffix.
*/
export default class UnitInput extends PureComponent {
static propTypes = {
children: PropTypes.node,
error: PropTypes.bool,
onBlur: PropTypes.func,
onChange: PropTypes.func,
placeholder: PropTypes.string,
suffix: PropTypes.string,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
}
static defaultProps = {
placeholder: '0',
}
constructor (props) {
super(props)
this.state = {
value: props.value || '',
}
}
componentDidUpdate (prevProps) {
const { value: prevPropsValue } = prevProps
const { value: propsValue } = this.props
const { value: stateValue } = this.state
if (prevPropsValue !== propsValue && propsValue !== stateValue) {
this.setState({ value: propsValue })
}
}
handleFocus = () => {
this.unitInput.focus()
}
handleChange = event => {
const { value: userInput } = event.target
let value = userInput
if (userInput.length && userInput.length > 1) {
value = removeLeadingZeroes(userInput)
}
this.setState({ value })
this.props.onChange(value)
}
handleBlur = event => {
const { onBlur } = this.props
typeof onBlur === 'function' && onBlur(this.state.value)
}
getInputWidth (value) {
const valueString = String(value)
const valueLength = valueString.length || 1
const decimalPointDeficit = valueString.match(/\./) ? -0.5 : 0
return (valueLength + decimalPointDeficit + 0.75) + 'ch'
}
render () {
const { error, placeholder, suffix, children } = this.props
const { value } = this.state
return (
<div
className={classnames('unit-input', { 'unit-input--error': error })}
onClick={this.handleFocus}
>
<div className="unit-input__input-container">
<input
type="number"
className="unit-input__input"
value={value}
placeholder={placeholder}
onChange={this.handleChange}
onBlur={this.handleBlur}
style={{ width: this.getInputWidth(value) }}
ref={ref => { this.unitInput = ref }}
/>
{
suffix && (
<div className="unit-input__suffix">
{ suffix }
</div>
)
}
</div>
{ children }
</div>
)
}
}

@ -0,0 +1 @@
export { default } from './user-preferenced-currency-display.container'

@ -0,0 +1,34 @@
import React from 'react'
import assert from 'assert'
import { shallow } from 'enzyme'
import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display.component'
import CurrencyDisplay from '../../currency-display'
describe('UserPreferencedCurrencyDisplay Component', () => {
describe('rendering', () => {
it('should render properly', () => {
const wrapper = shallow(
<UserPreferencedCurrencyDisplay />
)
assert.ok(wrapper)
assert.equal(wrapper.find(CurrencyDisplay).length, 1)
})
it('should pass all props to the CurrencyDisplay child component', () => {
const wrapper = shallow(
<UserPreferencedCurrencyDisplay
prop1={true}
prop2="test"
prop3={1}
/>
)
assert.ok(wrapper)
assert.equal(wrapper.find(CurrencyDisplay).length, 1)
assert.equal(wrapper.find(CurrencyDisplay).props().prop1, true)
assert.equal(wrapper.find(CurrencyDisplay).props().prop2, 'test')
assert.equal(wrapper.find(CurrencyDisplay).props().prop3, 1)
})
})
})

@ -0,0 +1,105 @@
import assert from 'assert'
import proxyquire from 'proxyquire'
let mapStateToProps, mergeProps
proxyquire('../user-preferenced-currency-display.container.js', {
'react-redux': {
connect: (ms, md, mp) => {
mapStateToProps = ms
mergeProps = mp
return () => ({})
},
},
})
describe('UserPreferencedCurrencyDisplay container', () => {
describe('mapStateToProps()', () => {
it('should return the correct props', () => {
const mockState = {
metamask: {
preferences: {
useETHAsPrimaryCurrency: true,
},
},
}
assert.deepEqual(mapStateToProps(mockState), {
useETHAsPrimaryCurrency: true,
})
})
})
describe('mergeProps()', () => {
it('should return the correct props', () => {
const mockDispatchProps = {}
const tests = [
{
stateProps: {
useETHAsPrimaryCurrency: true,
},
ownProps: {
type: 'PRIMARY',
},
result: {
currency: 'ETH',
numberOfDecimals: 6,
prefix: undefined,
},
},
{
stateProps: {
useETHAsPrimaryCurrency: false,
},
ownProps: {
type: 'PRIMARY',
},
result: {
currency: undefined,
numberOfDecimals: 2,
prefix: undefined,
},
},
{
stateProps: {
useETHAsPrimaryCurrency: true,
},
ownProps: {
type: 'SECONDARY',
fiatNumberOfDecimals: 4,
fiatPrefix: '-',
},
result: {
currency: undefined,
numberOfDecimals: 4,
prefix: '-',
},
},
{
stateProps: {
useETHAsPrimaryCurrency: false,
},
ownProps: {
type: 'SECONDARY',
fiatNumberOfDecimals: 4,
numberOfDecimals: 3,
fiatPrefix: 'a',
prefix: 'b',
},
result: {
currency: 'ETH',
numberOfDecimals: 3,
prefix: 'b',
},
},
]
tests.forEach(({ stateProps, ownProps, result }) => {
assert.deepEqual(mergeProps({ ...stateProps }, mockDispatchProps, { ...ownProps }), {
...result,
})
})
})
})
})

@ -0,0 +1,45 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import { PRIMARY, SECONDARY, ETH } from '../../constants/common'
import CurrencyDisplay from '../currency-display'
export default class UserPreferencedCurrencyDisplay extends PureComponent {
static propTypes = {
className: PropTypes.string,
prefix: PropTypes.string,
value: PropTypes.string,
numberOfDecimals: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
hideLabel: PropTypes.bool,
style: PropTypes.object,
showEthLogo: PropTypes.bool,
ethLogoHeight: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
// Used in container
type: PropTypes.oneOf([PRIMARY, SECONDARY]),
ethNumberOfDecimals: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
fiatNumberOfDecimals: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
ethPrefix: PropTypes.string,
fiatPrefix: PropTypes.string,
// From container
currency: PropTypes.string,
}
renderEthLogo () {
const { currency, showEthLogo, ethLogoHeight = 12 } = this.props
return currency === ETH && showEthLogo && (
<img
src="/images/eth.svg"
height={ethLogoHeight}
/>
)
}
render () {
return (
<CurrencyDisplay
{...this.props}
prefixComponent={this.renderEthLogo()}
/>
)
}
}

@ -0,0 +1,52 @@
import { connect } from 'react-redux'
import UserPreferencedCurrencyDisplay from './user-preferenced-currency-display.component'
import { preferencesSelector } from '../../selectors'
import { ETH, PRIMARY, SECONDARY } from '../../constants/common'
const mapStateToProps = (state, ownProps) => {
const { useETHAsPrimaryCurrency } = preferencesSelector(state)
return {
useETHAsPrimaryCurrency,
}
}
const mergeProps = (stateProps, dispatchProps, ownProps) => {
const { useETHAsPrimaryCurrency, ...restStateProps } = stateProps
const {
type,
numberOfDecimals: propsNumberOfDecimals,
ethNumberOfDecimals,
fiatNumberOfDecimals,
ethPrefix,
fiatPrefix,
prefix: propsPrefix,
...restOwnProps
} = ownProps
let currency, numberOfDecimals, prefix
if (type === PRIMARY && useETHAsPrimaryCurrency ||
type === SECONDARY && !useETHAsPrimaryCurrency) {
// Display ETH
currency = ETH
numberOfDecimals = propsNumberOfDecimals || ethNumberOfDecimals || 6
prefix = propsPrefix || ethPrefix
} else if (type === SECONDARY && useETHAsPrimaryCurrency ||
type === PRIMARY && !useETHAsPrimaryCurrency) {
// Display Fiat
numberOfDecimals = propsNumberOfDecimals || fiatNumberOfDecimals || 2
prefix = propsPrefix || fiatPrefix
}
return {
...restStateProps,
...dispatchProps,
...restOwnProps,
currency,
numberOfDecimals,
prefix,
}
}
export default connect(mapStateToProps, null, mergeProps)(UserPreferencedCurrencyDisplay)

@ -0,0 +1 @@
export { default } from './user-preferenced-currency-input.container'

@ -0,0 +1,32 @@
import React from 'react'
import assert from 'assert'
import { shallow } from 'enzyme'
import UserPreferencedCurrencyInput from '../user-preferenced-currency-input.component'
import CurrencyInput from '../../currency-input'
describe('UserPreferencedCurrencyInput Component', () => {
describe('rendering', () => {
it('should render properly', () => {
const wrapper = shallow(
<UserPreferencedCurrencyInput />
)
assert.ok(wrapper)
assert.equal(wrapper.find(CurrencyInput).length, 1)
})
it('should render useFiat for CurrencyInput based on preferences.useETHAsPrimaryCurrency', () => {
const wrapper = shallow(
<UserPreferencedCurrencyInput
useETHAsPrimaryCurrency
/>
)
assert.ok(wrapper)
assert.equal(wrapper.find(CurrencyInput).length, 1)
assert.equal(wrapper.find(CurrencyInput).props().useFiat, false)
wrapper.setProps({ useETHAsPrimaryCurrency: false })
assert.equal(wrapper.find(CurrencyInput).props().useFiat, true)
})
})
})

@ -0,0 +1,31 @@
import assert from 'assert'
import proxyquire from 'proxyquire'
let mapStateToProps
proxyquire('../user-preferenced-currency-input.container.js', {
'react-redux': {
connect: ms => {
mapStateToProps = ms
return () => ({})
},
},
})
describe('UserPreferencedCurrencyInput container', () => {
describe('mapStateToProps()', () => {
it('should return the correct props', () => {
const mockState = {
metamask: {
preferences: {
useETHAsPrimaryCurrency: true,
},
},
}
assert.deepEqual(mapStateToProps(mockState), {
useETHAsPrimaryCurrency: true,
})
})
})
})

@ -0,0 +1,20 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import CurrencyInput from '../currency-input'
export default class UserPreferencedCurrencyInput extends PureComponent {
static propTypes = {
useETHAsPrimaryCurrency: PropTypes.bool,
}
render () {
const { useETHAsPrimaryCurrency, ...restProps } = this.props
return (
<CurrencyInput
{...restProps}
useFiat={!useETHAsPrimaryCurrency}
/>
)
}
}

@ -0,0 +1,13 @@
import { connect } from 'react-redux'
import UserPreferencedCurrencyInput from './user-preferenced-currency-input.component'
import { preferencesSelector } from '../../selectors'
const mapStateToProps = state => {
const { useETHAsPrimaryCurrency } = preferencesSelector(state)
return {
useETHAsPrimaryCurrency,
}
}
export default connect(mapStateToProps)(UserPreferencedCurrencyInput)

@ -0,0 +1 @@
export { default } from './user-preferenced-token-input.container'

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

Loading…
Cancel
Save