Merge branch 'develop' into eth_chainid

feature/default_network_editable
hackyminer 6 years ago
commit bc7f8d0a5b
No known key found for this signature in database
GPG Key ID: 46D453DDCA4B766F
  1. 2
      .circleci/config.yml
  2. 3
      CHANGELOG.md
  3. 25
      app/_locales/en/messages.json
  4. 273
      app/_locales/ht/messages.json
  5. 42
      app/_locales/index.json
  6. 2
      app/_locales/nl/messages.json
  7. 12
      app/loading.html
  8. 10
      app/scripts/background.js
  9. 48
      app/scripts/controllers/currency.js
  10. 62
      app/scripts/controllers/network/network.js
  11. 70
      app/scripts/controllers/preferences.js
  12. 0
      app/scripts/lib/ens-ipfs/contracts/registrar.js
  13. 0
      app/scripts/lib/ens-ipfs/contracts/resolver.js
  14. 54
      app/scripts/lib/ens-ipfs/resolver.js
  15. 63
      app/scripts/lib/ens-ipfs/setup.js
  16. 46
      app/scripts/lib/ipfsContent.js
  17. 71
      app/scripts/lib/resolver.js
  18. 16
      app/scripts/metamask-controller.js
  19. 2
      development/states/add-token.json
  20. 2
      development/states/confirm-sig-requests.json
  21. 2
      development/states/currency-localization.json
  22. 2
      development/states/first-time.json
  23. 2
      development/states/send-new-ui.json
  24. 2
      development/states/tx-list-items.json
  25. 2
      old-ui/app/app.js
  26. 15
      old-ui/app/components/app-bar.js
  27. 11
      old-ui/app/components/balance.js
  28. 12
      old-ui/app/components/eth-balance.js
  29. 76
      old-ui/app/config.js
  30. 8
      old-ui/app/util.js
  31. 402
      package-lock.json
  32. 4
      package.json
  33. 2
      test/data/mock-state.json
  34. 2
      test/e2e/beta/from-import-beta-ui.spec.js
  35. 28
      test/e2e/beta/metamask-beta-ui.spec.js
  36. 2
      test/e2e/beta/run-drizzle.sh
  37. 2
      test/unit/app/controllers/network-contoller-test.js
  38. 24
      test/unit/app/controllers/preferences-controller-test.js
  39. 2
      test/unit/ui/app/actions.spec.js
  40. 12
      ui/app/actions.js
  41. 8
      ui/app/app.js
  42. 3
      ui/app/components/account-dropdowns.js
  43. 2
      ui/app/components/account-menu/index.js
  44. 2
      ui/app/components/account-panel.js
  45. 2
      ui/app/components/app-header/app-header.component.js
  46. 16
      ui/app/components/balance-component.js
  47. 17
      ui/app/components/currency-display/currency-display.component.js
  48. 5
      ui/app/components/currency-display/currency-display.container.js
  49. 4
      ui/app/components/currency-display/index.scss
  50. 13
      ui/app/components/currency-display/tests/currency-display.container.test.js
  51. 5
      ui/app/components/currency-input/currency-input.component.js
  52. 7
      ui/app/components/currency-input/currency-input.container.js
  53. 12
      ui/app/components/currency-input/tests/currency-input.component.test.js
  54. 5
      ui/app/components/currency-input/tests/currency-input.container.test.js
  55. 8
      ui/app/components/dropdowns/components/account-dropdowns.js
  56. 35
      ui/app/components/dropdowns/network-dropdown.js
  57. 4
      ui/app/components/dropdowns/tests/network-dropdown.test.js
  58. 12
      ui/app/components/eth-balance.js
  59. 124
      ui/app/components/identicon.js
  60. 99
      ui/app/components/identicon/identicon.component.js
  61. 12
      ui/app/components/identicon/identicon.container.js
  62. 1
      ui/app/components/identicon/index.js
  63. 7
      ui/app/components/identicon/index.scss
  64. 33
      ui/app/components/identicon/tests/identicon.component.test.js
  65. 2
      ui/app/components/index.scss
  66. 1
      ui/app/components/jazzicon/index.js
  67. 69
      ui/app/components/jazzicon/jazzicon.component.js
  68. 2
      ui/app/components/modals/account-modal-container.js
  69. 2
      ui/app/components/modals/hide-token-confirmation-modal.js
  70. 14
      ui/app/components/modals/modal.js
  71. 1
      ui/app/components/modals/transaction-details/index.js
  72. 54
      ui/app/components/modals/transaction-details/transaction-details.component.js
  73. 4
      ui/app/components/modals/transaction-details/transaction-details.container.js
  74. 4
      ui/app/components/network-display/network-display.component.js
  75. 5
      ui/app/components/network.js
  76. 12
      ui/app/components/pages/settings/settings-tab/index.scss
  77. 114
      ui/app/components/pages/settings/settings-tab/settings-tab.component.js
  78. 14
      ui/app/components/pages/settings/settings-tab/settings-tab.container.js
  79. 2
      ui/app/components/send/account-list-item/account-list-item.container.js
  80. 1
      ui/app/components/send/account-list-item/tests/account-list-item-component.test.js
  81. 2
      ui/app/components/send/account-list-item/tests/account-list-item-container.test.js
  82. 1
      ui/app/components/send/send-content/send-amount-row/tests/send-amount-row-component.test.js
  83. 5
      ui/app/components/send/send.selectors.js
  84. 1
      ui/app/components/send/tests/send-selectors-test-data.js
  85. 10
      ui/app/components/send/tests/send-selectors.test.js
  86. 5
      ui/app/components/shift-list-item.js
  87. 2
      ui/app/components/signature-request.js
  88. 2
      ui/app/components/token-cell.js
  89. 3
      ui/app/components/transaction-activity-log/tests/transaction-activity-log.container.test.js
  90. 21
      ui/app/components/transaction-activity-log/transaction-activity-log.component.js
  91. 3
      ui/app/components/transaction-activity-log/transaction-activity-log.container.js
  92. 11
      ui/app/components/transaction-activity-log/transaction-activity-log.util.js
  93. 2
      ui/app/components/transaction-breakdown/index.js
  94. 7
      ui/app/components/transaction-breakdown/transaction-breakdown.component.js
  95. 11
      ui/app/components/transaction-breakdown/transaction-breakdown.container.js
  96. 2
      ui/app/components/transaction-list-item/index.scss
  97. 17
      ui/app/components/transaction-list-item/transaction-list-item.component.js
  98. 10
      ui/app/components/transaction-list-item/transaction-list-item.container.js
  99. 3
      ui/app/components/transaction-list/index.scss
  100. 9
      ui/app/components/transaction-view-balance/index.scss
  101. Some files were not shown because too many files have changed in this diff Show More

@ -324,7 +324,7 @@ jobs:
command: > command: >
git config user.name metamaskbot && git config user.name metamaskbot &&
git config user.email admin@metamask.io && git config user.email admin@metamask.io &&
gh-pages -d docs/jsdocs npm run publish-docs
test-unit: test-unit:
docker: docker:

@ -2,6 +2,9 @@
## Current Develop Branch ## Current Develop Branch
- [#5563](https://github.com/MetaMask/metamask-extension/pull/5563#pullrequestreview-166769174) Feature: improve Hatian Creole translations
- [#5559](https://github.com/MetaMask/metamask-extension/pull/5559) Localize language names in translation select list
## 4.16.0 Wednesday October 17 2018 ## 4.16.0 Wednesday October 17 2018
- Feature: Add toggle for primary currency (eth/fiat) - Feature: Add toggle for primary currency (eth/fiat)

@ -692,9 +692,27 @@
"newRecipient": { "newRecipient": {
"message": "New Recipient" "message": "New Recipient"
}, },
"newRPC": { "newNetwork": {
"message": "New Network"
},
"rpcURL": {
"message": "New RPC URL" "message": "New RPC URL"
}, },
"showAdvancedOptions": {
"message": "Show Advanced Options"
},
"hideAdvancedOptions": {
"message": "Hide Advanced Options"
},
"optionalChainId": {
"message": "ChainID (optional)"
},
"optionalSymbol": {
"message": "Symbol (optional)"
},
"optionalNickname": {
"message": "Nickname (optional)"
},
"next": { "next": {
"message": "Next" "message": "Next"
}, },
@ -803,7 +821,7 @@
"message": "Primary Currency" "message": "Primary Currency"
}, },
"primaryCurrencySettingDescription": { "primaryCurrencySettingDescription": {
"message": "Select ETH to prioritize displaying values in ETH. Select Fiat to prioritize displaying values in your selected currency." "message": "Select native to prioritize displaying values in the native currency of the chain (e.g. ETH). Select Fiat to prioritize displaying values in your selected fiat currency."
}, },
"privacyMsg": { "privacyMsg": {
"message": "Privacy Policy" "message": "Privacy Policy"
@ -1169,6 +1187,9 @@
"transactionUpdatedGas": { "transactionUpdatedGas": {
"message": "Transaction updated with a gas price of $1 on $2." "message": "Transaction updated with a gas price of $1 on $2."
}, },
"transactionErrored": {
"message": "Transaction encountered an error."
},
"transactions": { "transactions": {
"message": "transactions" "message": "transactions"
}, },

@ -14,6 +14,9 @@
"accountName": { "accountName": {
"message": "Non Kont" "message": "Non Kont"
}, },
"accountOptions": {
"message": "Opsyon kont"
},
"accountSelectionRequired": { "accountSelectionRequired": {
"message": "Ou bezwen chwazi yon kont!" "message": "Ou bezwen chwazi yon kont!"
}, },
@ -45,7 +48,7 @@
"message": "Kantite lajan + Gaz" "message": "Kantite lajan + Gaz"
}, },
"appDescription": { "appDescription": {
"message": "Ethereum Ekstansyon Navigatè", "message": "Ekstansyon Navigatè Ethereum",
"description": "The description of the application" "description": "The description of the application"
}, },
"appName": { "appName": {
@ -61,6 +64,12 @@
"attemptingConnect": { "attemptingConnect": {
"message": "Eseye konekte nan blockchain." "message": "Eseye konekte nan blockchain."
}, },
"attemptToCancel": {
"message": "Eseye anile?"
},
"attemptToCancelDescription": {
"message": "Soumèt tantativ sa a pa garanti ke yo pral anile tranzaksyon ou anile. Si tantativ anile an gen siksè, ou pral chaje frè yo tranzaksyon pi wo a."
},
"attributions": { "attributions": {
"message": "Atribisyon" "message": "Atribisyon"
}, },
@ -116,12 +125,24 @@
"cancel": { "cancel": {
"message": "Anile" "message": "Anile"
}, },
"cancelAttempt": {
"message": "Teste Anile"
},
"cancellationGasFee": {
"message": "Anilasyon Gaz Chaj"
},
"cancelN": {
"message": "Anile tout $ 1 tranzaksyon"
},
"classicInterface": { "classicInterface": {
"message": "Sèvi ak fas klasik la" "message": "Sèvi ak fas klasik la"
}, },
"clickCopy": { "clickCopy": {
"message": "Klike sou kopi" "message": "Klike sou kopi"
}, },
"clickToAdd": {
"message": "Klike sou $ 1 pou ajoute yo nan kont ou"
},
"close": { "close": {
"message": "Fèmen" "message": "Fèmen"
}, },
@ -144,7 +165,7 @@
"message": "Konfime Tranzaksyon" "message": "Konfime Tranzaksyon"
}, },
"connectHardwareWallet": { "connectHardwareWallet": {
"message": "Konekte Hardware Wallet" "message": "Konekte Materyèl Wallet"
}, },
"connect": { "connect": {
"message": "Konekte" "message": "Konekte"
@ -152,6 +173,21 @@
"connecting": { "connecting": {
"message": "Koneksyon..." "message": "Koneksyon..."
}, },
"connectingToKovan": {
"message": "Konekte nan Kovan Tès Rezo a"
},
"connectingToMainnet": {
"message": "Konekte ak Prensipal Ethereum Rezo a"
},
"connectingToRopsten": {
"message": "Konekte ak Ropsten Tès Rezo a"
},
"connectingToRinkeby": {
"message": "Konekte nan Rinkeby Tès Rezo a"
},
"connectingToUnknown": {
"message": "Konekte nan rezo enkoni"
},
"connectToLedger": { "connectToLedger": {
"message": "Konekte ak Ledger" "message": "Konekte ak Ledger"
}, },
@ -208,14 +244,20 @@
}, },
"crypto": { "crypto": {
"message": "Crypto", "message": "Crypto",
"description": "Change tip (cryptocurrencies)" "description": "Exchange type (cryptocurrencies)"
}, },
"currentConversion": { "currentConversion": {
"message": "Konvèsyon aktyèl" "message": "Konvèsyon aktyèl"
}, },
"currentLanguage": {
"message": "Lang Aktyèl"
},
"currentNetwork": { "currentNetwork": {
"message": "Rezo aktyèl" "message": "Rezo aktyèl"
}, },
"currentRpc": {
"message": "Aktyèl RPC"
},
"customGas": { "customGas": {
"message": "Koutim Gaz" "message": "Koutim Gaz"
}, },
@ -322,14 +364,8 @@
"enterPasswordContinue": { "enterPasswordContinue": {
"message": "Mete modpas pou kontinye" "message": "Mete modpas pou kontinye"
}, },
"parameters": { "eth": {
"message": "Paramèt" "message": "ETH"
},
"passwordNotLongEnough": {
"message": "Modpas la pa ase"
},
"passwordsDontMatch": {
"message": "Modpas Pa Koresponn ak"
}, },
"etherscanView": { "etherscanView": {
"message": "Gade kont sou Etherscan" "message": "Gade kont sou Etherscan"
@ -337,6 +373,9 @@
"exchangeRate": { "exchangeRate": {
"message": "Chanje to" "message": "Chanje to"
}, },
"expandView": {
"message": "Elaji Wè"
},
"exportPrivateKey": { "exportPrivateKey": {
"message": "Voye Kòd Prive" "message": "Voye Kòd Prive"
}, },
@ -391,8 +430,11 @@
"gasLimitTooLow": { "gasLimitTooLow": {
"message": "Limit gaz dwe omwen 21000" "message": "Limit gaz dwe omwen 21000"
}, },
"gasUsed": {
"message": "Gaz yo Itilize"
},
"generatingSeed": { "generatingSeed": {
"message": "Génération Seed..." "message": "Grenn jenerasyon..."
}, },
"gasPrice": { "gasPrice": {
"message": "Pri gaz (GWEI)" "message": "Pri gaz (GWEI)"
@ -421,16 +463,16 @@
"description": "helper for inputting hex as decimal input" "description": "helper for inputting hex as decimal input"
}, },
"hardware": { "hardware": {
"message": "hardware" "message": "materyèl"
}, },
"hardwareWalletConnected": { "hardwareWalletConnected": {
"message": "Hardware Wallet konekte" "message": "Materyèl Wallet konekte"
}, },
"hardwareWallets": { "hardwareWallets": {
"message": "Hardware Wallet konekte" "message": "Materyèl Wallet konekte"
}, },
"hardwareWalletsMsg": { "hardwareWalletsMsg": {
"message": "Chwazi yon Hardware Wallet ou ta renmen itilize ak MetaMask" "message": "Chwazi yon Materyèl Wallet ou ta renmen itilize ak MetaMask"
}, },
"havingTroubleConnecting": { "havingTroubleConnecting": {
"message": "Èske w gen pwoblèm pou konekte?" "message": "Èske w gen pwoblèm pou konekte?"
@ -486,6 +528,9 @@
"importUsingSeed": { "importUsingSeed": {
"message": "Pòte lè sèvi avèk seed fraz" "message": "Pòte lè sèvi avèk seed fraz"
}, },
"info": {
"message": "Enfo"
},
"infoHelp": { "infoHelp": {
"message": "Enfo & Èd" "message": "Enfo & Èd"
}, },
@ -579,7 +624,7 @@
"message": "seed mo sèlman gen karaktè miniskil" "message": "seed mo sèlman gen karaktè miniskil"
}, },
"mainnet": { "mainnet": {
"message": "Main Ethereum Network" "message": "Prensipal Ethereum Rezo a"
}, },
"menu": { "menu": {
"message": "Opsyon" "message": "Opsyon"
@ -593,9 +638,15 @@
"metamaskSeedWords": { "metamaskSeedWords": {
"message": "MetaMask Seed Mo" "message": "MetaMask Seed Mo"
}, },
"metamaskVersion": {
"message": "MetaMask Vèsyon"
},
"min": { "min": {
"message": "Minimòm" "message": "Minimòm"
}, },
"missingYourTokens": {
"message": "Ou pa wè token ou a?"
},
"myAccounts": { "myAccounts": {
"message": "Kont mwen" "message": "Kont mwen"
}, },
@ -635,6 +686,9 @@
"newPassword": { "newPassword": {
"message": "Nouvo modpas (minit 8)" "message": "Nouvo modpas (minit 8)"
}, },
"newPassword8Chars": {
"message": "Nouvo modpas (minit 8)"
},
"newRecipient": { "newRecipient": {
"message": "Nouvo Benefisyè" "message": "Nouvo Benefisyè"
}, },
@ -677,6 +731,13 @@
"oldUIMessage": { "oldUIMessage": {
"message": "Ou te retounen nan Ansyen Itilizatè kouòdone. Ou ka chanje tounen nan nouvo Ansyen Itilizatè nan opsyon a nan meni an tèt la." "message": "Ou te retounen nan Ansyen Itilizatè kouòdone. Ou ka chanje tounen nan nouvo Ansyen Itilizatè nan opsyon a nan meni an tèt la."
}, },
"onlySendToEtherAddress": {
"message": "Sèlman voye ETH nan yon adrès Ethereum."
},
"onlySendTokensToAccountAddress": {
"message": "Sèlman voye $ 1 nan yon adrès kont Ethereum.",
"description": "displays token symbol"
},
"openInTab": { "openInTab": {
"message": "Louvri nan etikèt" "message": "Louvri nan etikèt"
}, },
@ -684,19 +745,34 @@
"message": "oubyen", "message": "oubyen",
"description": "choice between creating or importing a new account" "description": "choice between creating or importing a new account"
}, },
"orderOneHere": {
"message": "Mete nan lòd on Trezor oswa Ledger epi kenbe lajan ou nan yon stòk frèt."
},
"origin": { "origin": {
"message": "Orijin" "message": "Orijin"
}, },
"outgoing": {
"message": "Ap kite"
},
"parameters": {
"message": "Paramèt"
},
"password": { "password": {
"message": "Modpas" "message": "Modpas"
}, },
"passwordCorrect": { "passwordCorrect": {
"message": "Tanpri asire ke modpas ou kòrèk." "message": "Tanpri asire ke modpas ou kòrèk."
}, },
"passwordsDontMatch": {
"message": "Modpas pa matche"
},
"passwordMismatch": { "passwordMismatch": {
"message": "modpas sa pa menm", "message": "modpas sa pa menm",
"description": "in password creation process, the two new password fields did not match" "description": "in password creation process, the two new password fields did not match"
}, },
"passwordNotLongEnough": {
"message": "Modpas pa lontan ase"
},
"passwordShort": { "passwordShort": {
"message": "modpas pa sifi", "message": "modpas pa sifi",
"description": "in password creation process, the password is not long enough to be secure" "description": "in password creation process, the password is not long enough to be secure"
@ -723,6 +799,12 @@
"prev": { "prev": {
"message": "Avan" "message": "Avan"
}, },
"primaryCurrencySetting": {
"message": "Lajan ou itilize pi plis la"
},
"primaryCurrencySettingDescription": {
"message": "Chwazi ETH pou bay priyorite montre valè nan ETH. Chwazi Fiat priyorite montre valè nan lajan ou chwazi a."
},
"privacyMsg": { "privacyMsg": {
"message": "Règleman sou enfòmasyon prive" "message": "Règleman sou enfòmasyon prive"
}, },
@ -760,9 +842,21 @@
"refundAddress": { "refundAddress": {
"message": "Adrès pou resevwa" "message": "Adrès pou resevwa"
}, },
"rejected": { "reject": {
"message": "Rejte" "message": "Rejte"
}, },
"rejectAll": {
"message": "Rejte Tout"
},
"rejectTxsN": {
"message": "Rejete $ 1 tranzaksyon"
},
"rejectTxsDescription": {
"message": "Ou se sou rejte $ 1 yon anpil nan tranzaksyon yo."
},
"rejected": {
"message": "Rejete"
},
"reset": { "reset": {
"message": "Repwograme" "message": "Repwograme"
}, },
@ -787,9 +881,6 @@
"retryWithMoreGas": { "retryWithMoreGas": {
"message": "Reseye ak yon pri gaz pi wo isit la" "message": "Reseye ak yon pri gaz pi wo isit la"
}, },
"walletSeed": {
"message": "Wallet Seed"
},
"restore": { "restore": {
"message": "Retabli" "message": "Retabli"
}, },
@ -832,24 +923,6 @@
"rpc": { "rpc": {
"message": "Koutim RPC" "message": "Koutim RPC"
}, },
"currentRpc": {
"message": "Kounya RPC"
},
"connectingToMainnet": {
"message": "Konekte ak Main (Prensipal) Ethereum Rezo a"
},
"connectingToRopsten": {
"message": "Konekte ak Ropsten Tès Rezo a"
},
"connectingToKovan": {
"message": "Konekte nan Kovan Tès Rezo a"
},
"connectingToRinkeby": {
"message": "Konekte nan Rinkeby Tès Rezo a"
},
"connectingToUnknown": {
"message": "Konekte nan rezo enkoni"
},
"sampleAccountName": { "sampleAccountName": {
"message": "Pa egzanp, Nouvo kont mwen an", "message": "Pa egzanp, Nouvo kont mwen an",
"description": "Help user understand concept of adding a human-readable name to their account" "description": "Help user understand concept of adding a human-readable name to their account"
@ -857,15 +930,6 @@
"save": { "save": {
"message": "Sove" "message": "Sove"
}, },
"speedUp": {
"message": "pi vit"
},
"speedUpTitle": {
"message": "Monte vitès tranzaksyon"
},
"speedUpSubtitle": {
"message": "Ogmante pri gaz ou pou eseye efase tranzaksyon ou pi vit"
},
"saveAsCsvFile": { "saveAsCsvFile": {
"message": "Sove kòm dosye CSV" "message": "Sove kòm dosye CSV"
}, },
@ -876,6 +940,12 @@
"saveSeedAsFile": { "saveSeedAsFile": {
"message": "Sove pawòl seed kòm dosye" "message": "Sove pawòl seed kòm dosye"
}, },
"scanInstructions": {
"message": "Mete kòd QR la devan kamera ou"
},
"scanQrCode": {
"message": "Enspeksyon QR Kòd"
},
"search": { "search": {
"message": "Rechèch" "message": "Rechèch"
}, },
@ -885,15 +955,6 @@
"secretPhrase": { "secretPhrase": {
"message": "Antre fraz sekrè douz mo ou a pou w restore kòf ou a." "message": "Antre fraz sekrè douz mo ou a pou w restore kòf ou a."
}, },
"showHexData": {
"message": "Montre Hex Data"
},
"showHexDataDescription": {
"message": "Pran sa pouw ka montre chan entèfas hex data a"
},
"newPassword8Chars": {
"message": "Nouvo modpas (pou pi pit 8)"
},
"seedPhraseReq": { "seedPhraseReq": {
"message": "Seed fraz yo se 12 long mo" "message": "Seed fraz yo se 12 long mo"
}, },
@ -903,6 +964,9 @@
"selectCurrency": { "selectCurrency": {
"message": "Chwazi Lajan" "message": "Chwazi Lajan"
}, },
"selectLocale": {
"message": "Chwazi Lokasyon"
},
"selectService": { "selectService": {
"message": "Chwazi Sèvis" "message": "Chwazi Sèvis"
}, },
@ -927,19 +991,6 @@
"separateEachWord": { "separateEachWord": {
"message": "Separe chak mo ak yon sèl espas" "message": "Separe chak mo ak yon sèl espas"
}, },
"onlySendToEtherAddress": {
"message": "Sèlman voye ETH nan yon adrès Ethereum."
},
"onlySendTokensToAccountAddress": {
"message": "Sèlman voye $ 1 nan yon adrès kont Ethereum.",
"description": "displays token symbol"
},
"orderOneHere": {
"message": "Mete nan lòd on Trezor oswa Ledger epi kenbe lajan ou nan yon stòk frèt."
},
"outgoing": {
"message": "Ap kite"
},
"searchTokens": { "searchTokens": {
"message": "Rechèch Tokens" "message": "Rechèch Tokens"
}, },
@ -964,33 +1015,6 @@
"settings": { "settings": {
"message": "Paramèt" "message": "Paramèt"
}, },
"step1HardwareWallet": {
"message": "1. Konekte Materyèl bous"
},
"step1HardwareWalletMsg": {
"message": "Konekte materyèl bous ou dirèkteman nan òdinatè ou."
},
"step2HardwareWallet": {
"message": "2. Chwazi yon kont"
},
"step2HardwareWalletMsg": {
"message": "Chwazi kont ou vle wè a. Ou ka chwazi youn sèlman nan yon moman."
},
"step3HardwareWallet": {
"message": "3. Kòmanse itilize dApps ak plis ankò!"
},
"step3HardwareWalletMsg": {
"message": "Sèvi ak kont materyèl ou menm jan ou t ap fè pou kont Etherum. Ouvri sesyon an nan dApps, voye Eth, achte ak stòke ERC20 tokens ak e ki pake chanje tokens tankou CryptoKitties."
},
"info": {
"message": "Enfòmasyon"
},
"scanInstructions": {
"message": "Mete kòd QR la devan kamera ou"
},
"scanQrCode": {
"message": "Enspeksyon QR Kòd"
},
"shapeshiftBuy": { "shapeshiftBuy": {
"message": "Achte avèk Shapeshift" "message": "Achte avèk Shapeshift"
}, },
@ -1000,6 +1024,12 @@
"showQRCode": { "showQRCode": {
"message": "Montre Kòd QR" "message": "Montre Kòd QR"
}, },
"showHexData": {
"message": "Montre Hex Data"
},
"showHexDataDescription": {
"message": "Pran sa pouw ka montre chan entèfas hex data a"
},
"sign": { "sign": {
"message": "Siyen" "message": "Siyen"
}, },
@ -1024,6 +1054,15 @@
"spaceBetween": { "spaceBetween": {
"message": "ka gen sèlman yon espas ant mo yo" "message": "ka gen sèlman yon espas ant mo yo"
}, },
"speedUp": {
"message": "pi vit"
},
"speedUpTitle": {
"message": "Monte vitès tranzaksyon"
},
"speedUpSubtitle": {
"message": "Ogmante pri gaz ou pou eseye efase tranzaksyon ou pi vit"
},
"status": { "status": {
"message": "Kondisyon" "message": "Kondisyon"
}, },
@ -1036,6 +1075,24 @@
"stateLogError": { "stateLogError": {
"message": "Erè nan retwouve State Logs yo." "message": "Erè nan retwouve State Logs yo."
}, },
"step1HardwareWallet": {
"message": "1. Konekte Materyèl bous"
},
"step1HardwareWalletMsg": {
"message": "Konekte materyèl bous ou dirèkteman nan òdinatè ou."
},
"step2HardwareWallet": {
"message": "2. Chwazi yon kont"
},
"step2HardwareWalletMsg": {
"message": "Chwazi kont ou vle wè a. Ou ka chwazi youn sèlman nan yon moman."
},
"step3HardwareWallet": {
"message": "3. Kòmanse itilize dApps ak plis ankò!"
},
"step3HardwareWalletMsg": {
"message": "Sèvi ak kont materyèl ou menm jan ou t ap fè pou kont Etherum. Ouvri sesyon an nan dApps, voye Eth, achte ak stòke ERC20 tokens ak e ki pake chanje tokens tankou CryptoKitties."
},
"submit": { "submit": {
"message": "Soumèt" "message": "Soumèt"
}, },
@ -1088,7 +1145,7 @@
"total": { "total": {
"message": "Total" "message": "Total"
}, },
"transactions": { "transaction": {
"message": "tranzaksyon yo" "message": "tranzaksyon yo"
}, },
"transactionConfirmed": { "transactionConfirmed": {
@ -1097,6 +1154,9 @@
"transactionCreated": { "transactionCreated": {
"message": "Tranzaksyon ou te kreye avèk on valè de $1 pou $2." "message": "Tranzaksyon ou te kreye avèk on valè de $1 pou $2."
}, },
"transactionWithNonce": {
"message": "Tranzaksyon $1"
},
"transactionDropped": { "transactionDropped": {
"message": "Tranzaksyon ou te tonbe a $2." "message": "Tranzaksyon ou te tonbe a $2."
}, },
@ -1109,6 +1169,9 @@
"transactionUpdatedGas": { "transactionUpdatedGas": {
"message": "Tranzaksyon ou te aktyalize avèk on pri gaz de $1 a $2." "message": "Tranzaksyon ou te aktyalize avèk on pri gaz de $1 a $2."
}, },
"transactions": {
"message": "transactions"
},
"transactionError": { "transactionError": {
"message": "Erè tranzaksyon. Eksepsyon jete nan kòd kontra." "message": "Erè tranzaksyon. Eksepsyon jete nan kòd kontra."
}, },
@ -1121,6 +1184,9 @@
"transfer": { "transfer": {
"message": "Transfè" "message": "Transfè"
}, },
"transferFrom": {
"message": "Transfer From"
},
"transfers": { "transfers": {
"message": "Transfè yo" "message": "Transfè yo"
}, },
@ -1182,6 +1248,9 @@
"unlockMessage": { "unlockMessage": {
"message": "Entènèt desantralize a ap tann" "message": "Entènèt desantralize a ap tann"
}, },
"updatedWithDate": {
"message": "Mete ajou $1"
},
"uriErrorMsg": { "uriErrorMsg": {
"message": "URIs mande pou apwopriye prefiks HTTP / HTTPS a." "message": "URIs mande pou apwopriye prefiks HTTP / HTTPS a."
}, },
@ -1210,11 +1279,14 @@
"visitWebSite": { "visitWebSite": {
"message": "Vizite sit entènèt nou an" "message": "Vizite sit entènèt nou an"
}, },
"walletSeed": {
"message": "Bous Seed"
},
"warning": { "warning": {
"message": "Avètisman" "message": "Avètisman"
}, },
"welcomeBack": { "welcomeBack": {
"message": "Bon retou!" "message": "Bon Retou!"
}, },
"welcomeBeta": { "welcomeBeta": {
"message": "Byenveni nan MetaMask Beta" "message": "Byenveni nan MetaMask Beta"
@ -1222,6 +1294,9 @@
"whatsThis": { "whatsThis": {
"message": "Kisa sa ye?" "message": "Kisa sa ye?"
}, },
"yesLetsTry": {
"message": "Wi, ann eseye"
},
"youNeedToAllowCameraAccess": { "youNeedToAllowCameraAccess": {
"message": "Ou bezwen bay kamera aksè pou sèvi ak fonksyon sa." "message": "Ou bezwen bay kamera aksè pou sèvi ak fonksyon sa."
}, },

@ -1,24 +1,24 @@
[ [
{ "code": "cs", "name": "Czech" }, { "code": "cs", "name": "Čeština" },
{ "code": "de", "name": "German" }, { "code": "de", "name": "Deutsche" },
{ "code": "en", "name": "English" }, { "code": "en", "name": "English" },
{ "code": "es", "name": "Spanish" }, { "code": "es", "name": "Español" },
{ "code": "fr", "name": "French" }, { "code": "fr", "name": "Français" },
{ "code": "ht", "name": "Haitian Creole" }, { "code": "ht", "name": "Kreyòl ayisyen" },
{ "code": "hn", "name": "Hindi" }, { "code": "hn", "name": "ि" },
{ "code": "it", "name": "Italian" }, { "code": "it", "name": "Italiano" },
{ "code": "ja", "name": "Japanese" }, { "code": "ja", "name": "日本語" },
{ "code": "ko", "name": "Korean" }, { "code": "ko", "name": "한국어" },
{ "code": "nl", "name": "Dutch" }, { "code": "nl", "name": "Nederlands" },
{ "code": "ph", "name": "Tagalog" }, { "code": "ph", "name": "Pilipino" },
{ "code": "pl", "name": "Polish" }, { "code": "pl", "name": "Polskie" },
{ "code": "pt", "name": "Portuguese" }, { "code": "pt", "name": "Português" },
{ "code": "ru", "name": "Russian" }, { "code": "ru", "name": "Русский" },
{ "code": "sl", "name": "Slovenian" }, { "code": "sl", "name": "Slovenščina" },
{ "code": "th", "name": "Thai" }, { "code": "th", "name": "ไทย" },
{ "code": "tml", "name": "Tamil" }, { "code": "tml", "name": "தமி" },
{ "code": "tr", "name": "Turkish" }, { "code": "tr", "name": "Türkçe" },
{ "code": "vi", "name": "Vietnamese" }, { "code": "vi", "name": "Tiếng Việt" },
{ "code": "zh_CN", "name": "Chinese (Simplified)" }, { "code": "zh_CN", "name": "中文(简体)" },
{ "code": "zh_TW", "name": "Chinese (Traditional)" } { "code": "zh_TW", "name": "中文(繁體)" }
] ]

@ -435,7 +435,7 @@
"message": "back-up woorden hebben alleen kleine letters" "message": "back-up woorden hebben alleen kleine letters"
}, },
"mainnet": { "mainnet": {
"message": "belangrijkste Ethereum-netwerk" "message": "Main Netwerk"
}, },
"message": { "message": {
"message": "Bericht" "message": "Bericht"

@ -1,5 +1,9 @@
<html> <!DOCTYPE html>
<html lang="en">
<head> <head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>MetaMask Loading</title> <title>MetaMask Loading</title>
<style> <style>
#div-logo { #div-logo {
@ -31,5 +35,11 @@
<div id="div-logo"> <div id="div-logo">
<img id="logo" src="./images/loginglogo.svg"> <img id="logo" src="./images/loginglogo.svg">
</div> </div>
<script type="text/javascript">
// redirect to 404 after one minute
setTimeout(() => {
location.href = './404.html'
}, 60000)
</script>
</body> </body>
</html> </html>

@ -29,7 +29,7 @@ const setupMetamaskMeshMetrics = require('./lib/setupMetamaskMeshMetrics')
const EdgeEncryptor = require('./edge-encryptor') const EdgeEncryptor = require('./edge-encryptor')
const getFirstPreferredLangCode = require('./lib/get-first-preferred-lang-code') const getFirstPreferredLangCode = require('./lib/get-first-preferred-lang-code')
const getObjStructure = require('./lib/getObjStructure') const getObjStructure = require('./lib/getObjStructure')
const ipfsContent = require('./lib/ipfsContent.js') const setupEnsIpfsResolver = require('./lib/ens-ipfs/setup')
const { const {
ENVIRONMENT_TYPE_POPUP, ENVIRONMENT_TYPE_POPUP,
@ -58,7 +58,6 @@ const isIE = !!document.documentMode
// Edge 20+ // Edge 20+
const isEdge = !isIE && !!window.StyleMedia const isEdge = !isIE && !!window.StyleMedia
let ipfsHandle
let popupIsOpen = false let popupIsOpen = false
let notificationIsOpen = false let notificationIsOpen = false
const openMetamaskTabsIDs = {} const openMetamaskTabsIDs = {}
@ -164,7 +163,6 @@ async function initialize () {
const initLangCode = await getFirstPreferredLangCode() const initLangCode = await getFirstPreferredLangCode()
await setupController(initState, initLangCode) await setupController(initState, initLangCode)
log.debug('MetaMask initialization complete.') log.debug('MetaMask initialization complete.')
ipfsHandle = ipfsContent(initState.NetworkController.provider)
} }
// //
@ -269,10 +267,8 @@ function setupController (initState, initLangCode) {
}) })
global.metamaskController = controller global.metamaskController = controller
controller.networkController.on('networkDidChange', () => { const provider = controller.provider
ipfsHandle && ipfsHandle.remove() setupEnsIpfsResolver({ provider })
ipfsHandle = ipfsContent(controller.networkController.providerStore.getState())
})
// report failed transactions to Sentry // report failed transactions to Sentry
controller.txController.on(`tx:status-update`, (txId, status) => { controller.txController.on(`tx:status-update`, (txId, status) => {

@ -21,6 +21,7 @@ class CurrencyController {
* since midnight of January 1, 1970 * since midnight of January 1, 1970
* @property {number} conversionInterval The id of the interval created by the scheduleConversionInterval method. * @property {number} conversionInterval The id of the interval created by the scheduleConversionInterval method.
* Used to clear an existing interval on subsequent calls of that method. * Used to clear an existing interval on subsequent calls of that method.
* @property {string} nativeCurrency The ticker/symbol of the native chain currency
* *
*/ */
constructor (opts = {}) { constructor (opts = {}) {
@ -28,6 +29,7 @@ class CurrencyController {
currentCurrency: 'usd', currentCurrency: 'usd',
conversionRate: 0, conversionRate: 0,
conversionDate: 'N/A', conversionDate: 'N/A',
nativeCurrency: 'ETH',
}, opts.initState) }, opts.initState)
this.store = new ObservableStore(initState) this.store = new ObservableStore(initState)
} }
@ -36,6 +38,29 @@ class CurrencyController {
// PUBLIC METHODS // PUBLIC METHODS
// //
/**
* A getter for the nativeCurrency property
*
* @returns {string} A 2-4 character shorthand that describes the specific currency
*
*/
getNativeCurrency () {
return this.store.getState().nativeCurrency
}
/**
* A setter for the nativeCurrency property
*
* @param {string} nativeCurrency The new currency to set as the nativeCurrency in the store
*
*/
setNativeCurrency (nativeCurrency) {
this.store.updateState({
nativeCurrency,
ticker: nativeCurrency,
})
}
/** /**
* A getter for the currentCurrency property * A getter for the currentCurrency property
* *
@ -104,15 +129,32 @@ class CurrencyController {
* *
*/ */
async updateConversionRate () { async updateConversionRate () {
let currentCurrency let currentCurrency, nativeCurrency
try { try {
currentCurrency = this.getCurrentCurrency() currentCurrency = this.getCurrentCurrency()
const response = await fetch(`https://api.infura.io/v1/ticker/eth${currentCurrency.toLowerCase()}`) nativeCurrency = this.getNativeCurrency()
let apiUrl
if (nativeCurrency === 'ETH') {
apiUrl = `https://api.infura.io/v1/ticker/eth${currentCurrency.toLowerCase()}`
} else {
apiUrl = `https://min-api.cryptocompare.com/data/price?fsym=${nativeCurrency.toUpperCase()}&tsyms=${currentCurrency.toUpperCase()}`
}
const response = await fetch(apiUrl)
const parsedResponse = await response.json() const parsedResponse = await response.json()
if (nativeCurrency === 'ETH') {
this.setConversionRate(Number(parsedResponse.bid)) this.setConversionRate(Number(parsedResponse.bid))
this.setConversionDate(Number(parsedResponse.timestamp)) this.setConversionDate(Number(parsedResponse.timestamp))
} else {
if (parsedResponse[currentCurrency.toUpperCase()]) {
this.setConversionRate(Number(parsedResponse[currentCurrency.toUpperCase()]))
this.setConversionDate(parseInt((new Date()).getTime() / 1000))
} else {
this.setConversionRate(0)
this.setConversionDate('N/A')
}
}
} catch (err) { } catch (err) {
log.warn(`MetaMask - Failed to query currency conversion:`, currentCurrency, err) log.warn(`MetaMask - Failed to query currency conversion:`, nativeCurrency, currentCurrency, err)
this.setConversionRate(0) this.setConversionRate(0)
this.setConversionDate('N/A') this.setConversionDate('N/A')
} }

@ -11,6 +11,8 @@ const createInfuraClient = require('./createInfuraClient')
const createJsonRpcClient = require('./createJsonRpcClient') const createJsonRpcClient = require('./createJsonRpcClient')
const createLocalhostClient = require('./createLocalhostClient') const createLocalhostClient = require('./createLocalhostClient')
const { createSwappableProxy, createEventEmitterProxy } = require('swappable-obj-proxy') const { createSwappableProxy, createEventEmitterProxy } = require('swappable-obj-proxy')
const extend = require('extend')
const networks = { networkList: {} }
const { const {
ROPSTEN, ROPSTEN,
@ -29,6 +31,10 @@ const defaultProviderConfig = {
type: testMode ? RINKEBY : MAINNET, type: testMode ? RINKEBY : MAINNET,
} }
const defaultNetworkConfig = {
ticker: 'ETH',
}
module.exports = class NetworkController extends EventEmitter { module.exports = class NetworkController extends EventEmitter {
constructor (opts = {}) { constructor (opts = {}) {
@ -39,7 +45,8 @@ module.exports = class NetworkController extends EventEmitter {
// create stores // create stores
this.providerStore = new ObservableStore(providerConfig) this.providerStore = new ObservableStore(providerConfig)
this.networkStore = new ObservableStore('loading') this.networkStore = new ObservableStore('loading')
this.store = new ComposedStore({ provider: this.providerStore, network: this.networkStore }) this.networkConfig = new ObservableStore(defaultNetworkConfig)
this.store = new ComposedStore({ provider: this.providerStore, network: this.networkStore, settings: this.networkConfig })
this.on('networkDidChange', this.lookupNetwork) this.on('networkDidChange', this.lookupNetwork)
// provider and block tracker // provider and block tracker
this._provider = null this._provider = null
@ -51,8 +58,8 @@ module.exports = class NetworkController extends EventEmitter {
initializeProvider (providerParams) { initializeProvider (providerParams) {
this._baseProviderParams = providerParams this._baseProviderParams = providerParams
const { type, rpcTarget } = this.providerStore.getState() const { type, rpcTarget, chainId, ticker, nickname } = this.providerStore.getState()
this._configureProvider({ type, rpcTarget }) this._configureProvider({ type, rpcTarget, chainId, ticker, nickname })
this.lookupNetwork() this.lookupNetwork()
} }
@ -72,7 +79,20 @@ module.exports = class NetworkController extends EventEmitter {
return this.networkStore.getState() return this.networkStore.getState()
} }
setNetworkState (network) { getNetworkConfig () {
return this.networkConfig.getState()
}
setNetworkState (network, type) {
if (network === 'loading') {
return this.networkStore.putState(network)
}
// type must be defined
if (!type) {
return
}
network = networks.networkList[type] && networks.networkList[type].chainId ? networks.networkList[type].chainId : network
return this.networkStore.putState(network) return this.networkStore.putState(network)
} }
@ -85,6 +105,7 @@ module.exports = class NetworkController extends EventEmitter {
if (!this._provider) { if (!this._provider) {
return log.warn('NetworkController - lookupNetwork aborted due to missing provider') return log.warn('NetworkController - lookupNetwork aborted due to missing provider')
} }
var { type } = this.providerStore.getState()
const ethQuery = new EthQuery(this._provider) const ethQuery = new EthQuery(this._provider)
// first attempt to perform lookup via eth_chainId // first attempt to perform lookup via eth_chainId
ethQuery.sendAsync({ method: 'eth_chainId' }, (err, chainIdHex) => { ethQuery.sendAsync({ method: 'eth_chainId' }, (err, chainIdHex) => {
@ -93,20 +114,23 @@ module.exports = class NetworkController extends EventEmitter {
ethQuery.sendAsync({ method: 'net_version' }, (err, network) => { ethQuery.sendAsync({ method: 'net_version' }, (err, network) => {
if (err) return this.setNetworkState('loading') if (err) return this.setNetworkState('loading')
log.info(`net_version returned ${network}`) log.info(`net_version returned ${network}`)
this.setNetworkState(network) this.setNetworkState(network, type)
}) })
return return
} }
const chainId = Number.parseInt(chainIdHex, 16) const chainId = Number.parseInt(chainIdHex, 16)
log.info(`net_version returned ${chainId}`) log.info(`net_version returned ${chainId}`)
this.setNetworkState(chainId) this.setNetworkState(chainId, type)
}) })
} }
setRpcTarget (rpcTarget) { setRpcTarget (rpcTarget, chainId, ticker = 'ETH', nickname = '') {
const providerConfig = { const providerConfig = {
type: 'rpc', type: 'rpc',
rpcTarget, rpcTarget,
chainId,
ticker,
nickname,
} }
this.providerConfig = providerConfig this.providerConfig = providerConfig
} }
@ -142,7 +166,7 @@ module.exports = class NetworkController extends EventEmitter {
} }
_configureProvider (opts) { _configureProvider (opts) {
const { type, rpcTarget } = opts const { type, rpcTarget, chainId, ticker, nickname } = opts
// infura type-based endpoints // infura type-based endpoints
const isInfura = INFURA_PROVIDER_TYPES.includes(type) const isInfura = INFURA_PROVIDER_TYPES.includes(type)
if (isInfura) { if (isInfura) {
@ -152,7 +176,7 @@ module.exports = class NetworkController extends EventEmitter {
this._configureLocalhostProvider() this._configureLocalhostProvider()
// url-based rpc endpoints // url-based rpc endpoints
} else if (type === 'rpc') { } else if (type === 'rpc') {
this._configureStandardProvider({ rpcUrl: rpcTarget }) this._configureStandardProvider({ rpcUrl: rpcTarget, chainId, ticker, nickname })
} else { } else {
throw new Error(`NetworkController - _configureProvider - unknown type "${type}"`) throw new Error(`NetworkController - _configureProvider - unknown type "${type}"`)
} }
@ -162,6 +186,11 @@ module.exports = class NetworkController extends EventEmitter {
log.info('NetworkController - configureInfuraProvider', type) log.info('NetworkController - configureInfuraProvider', type)
const networkClient = createInfuraClient({ network: type }) const networkClient = createInfuraClient({ network: type })
this._setNetworkClient(networkClient) this._setNetworkClient(networkClient)
// setup networkConfig
var settings = {
ticker: 'ETH',
}
this.networkConfig.putState(settings)
} }
_configureLocalhostProvider () { _configureLocalhostProvider () {
@ -170,9 +199,22 @@ module.exports = class NetworkController extends EventEmitter {
this._setNetworkClient(networkClient) this._setNetworkClient(networkClient)
} }
_configureStandardProvider ({ rpcUrl }) { _configureStandardProvider ({ rpcUrl, chainId, ticker, nickname }) {
log.info('NetworkController - configureStandardProvider', rpcUrl) log.info('NetworkController - configureStandardProvider', rpcUrl)
const networkClient = createJsonRpcClient({ rpcUrl }) const networkClient = createJsonRpcClient({ rpcUrl })
// hack to add a 'rpc' network with chainId
networks.networkList['rpc'] = {
chainId: chainId,
rpcUrl,
ticker: ticker || 'ETH',
nickname,
}
// setup networkConfig
var settings = {
network: chainId,
}
settings = extend(settings, networks.networkList['rpc'])
this.networkConfig.putState(settings)
this._setNetworkClient(networkClient) this._setNetworkClient(networkClient)
} }

@ -25,7 +25,7 @@ class PreferencesController {
*/ */
constructor (opts = {}) { constructor (opts = {}) {
const initState = extend({ const initState = extend({
frequentRpcList: [], frequentRpcListDetail: [],
currentAccountTab: 'history', currentAccountTab: 'history',
accountTokens: {}, accountTokens: {},
assetImages: {}, assetImages: {},
@ -39,7 +39,7 @@ class PreferencesController {
seedWords: null, seedWords: null,
forgottenPassword: false, forgottenPassword: false,
preferences: { preferences: {
useETHAsPrimaryCurrency: true, useNativeCurrencyAsPrimaryCurrency: true,
}, },
}, opts.initState) }, opts.initState)
@ -104,7 +104,7 @@ class PreferencesController {
* @param {Function} - end * @param {Function} - end
*/ */
async requestWatchAsset (req, res, next, end) { async requestWatchAsset (req, res, next, end) {
if (req.method === 'metamask_watchAsset') { if (req.method === 'metamask_watchAsset' || req.method === 'wallet_watchAsset') {
const { type, options } = req.params const { type, options } = req.params
switch (type) { switch (type) {
case 'ERC20': case 'ERC20':
@ -374,22 +374,6 @@ class PreferencesController {
return Promise.resolve(label) return Promise.resolve(label)
} }
/**
* Gets an updated rpc list from this.addToFrequentRpcList() and sets the `frequentRpcList` to this update list.
*
* @param {string} _url The the new rpc url to add to the updated list
* @param {bool} remove Remove selected url
* @returns {Promise<void>} Promise resolves with undefined
*
*/
updateFrequentRpcList (_url, remove = false) {
return this.addToFrequentRpcList(_url, remove)
.then((rpcList) => {
this.store.updateState({ frequentRpcList: rpcList })
return Promise.resolve()
})
}
/** /**
* Setter for the `currentAccountTab` property * Setter for the `currentAccountTab` property
* *
@ -405,35 +389,53 @@ class PreferencesController {
} }
/** /**
* Returns an updated rpcList based on the passed url and the current list. * Adds custom RPC url to state.
* The returned list will have a max length of 3. If the _url currently exists it the list, it will be moved to the
* end of the list. The current list is modified and returned as a promise.
* *
* @param {string} _url The rpc url to add to the frequentRpcList. * @param {string} url The RPC url to add to frequentRpcList.
* @param {bool} remove Remove selected url * @param {number} chainId Optional chainId of the selected network.
* @returns {Promise<array>} The updated frequentRpcList. * @param {string} ticker Optional ticker symbol of the selected network.
* @param {string} nickname Optional nickname of the selected network.
* @returns {Promise<array>} Promise resolving to updated frequentRpcList.
* *
*/ */
addToFrequentRpcList (_url, remove = false) { addToFrequentRpcList (url, chainId, ticker = 'ETH', nickname = '') {
const rpcList = this.getFrequentRpcList() const rpcList = this.getFrequentRpcListDetail()
const index = rpcList.findIndex((element) => { return element === _url }) const index = rpcList.findIndex((element) => { return element.rpcUrl === url })
if (index !== -1) { if (index !== -1) {
rpcList.splice(index, 1) rpcList.splice(index, 1)
} }
if (!remove && _url !== 'http://localhost:8545') { if (url !== 'http://localhost:8545') {
rpcList.push(_url) rpcList.push({ rpcUrl: url, chainId, ticker, nickname })
}
this.store.updateState({ frequentRpcListiDetail: rpcList })
return Promise.resolve(rpcList)
}
/**
* Removes custom RPC url from state.
*
* @param {string} url The RPC url to remove from frequentRpcList.
* @returns {Promise<array>} Promise resolving to updated frequentRpcList.
*
*/
removeFromFrequentRpcList (url) {
const rpcList = this.getFrequentRpcListDetail()
const index = rpcList.findIndex((element) => { return element.rpcUrl === url })
if (index !== -1) {
rpcList.splice(index, 1)
} }
this.store.updateState({ frequentRpcListDetail: rpcList })
return Promise.resolve(rpcList) return Promise.resolve(rpcList)
} }
/** /**
* Getter for the `frequentRpcList` property. * Getter for the `frequentRpcListDetail` property.
* *
* @returns {array<string>} An array of one or two rpc urls. * @returns {array<array>} An array of rpc urls.
* *
*/ */
getFrequentRpcList () { getFrequentRpcListDetail () {
return this.store.getState().frequentRpcList return this.store.getState().frequentRpcListDetail
} }
/** /**

@ -0,0 +1,54 @@
const namehash = require('eth-ens-namehash')
const multihash = require('multihashes')
const Eth = require('ethjs-query')
const EthContract = require('ethjs-contract')
const registrarAbi = require('./contracts/registrar')
const resolverAbi = require('./contracts/resolver')
module.exports = resolveEnsToIpfsContentId
async function resolveEnsToIpfsContentId ({ provider, name }) {
const eth = new Eth(provider)
const hash = namehash.hash(name)
const contract = new EthContract(eth)
// lookup registrar
const chainId = Number.parseInt(await eth.net_version(), 10)
const registrarAddress = getRegistrarForChainId(chainId)
if (!registrarAddress) {
throw new Error(`EnsIpfsResolver - no known ens-ipfs registrar for chainId "${chainId}"`)
}
const Registrar = contract(registrarAbi).at(registrarAddress)
// lookup resolver
const resolverLookupResult = await Registrar.resolver(hash)
const resolverAddress = resolverLookupResult[0]
if (hexValueIsEmpty(resolverAddress)) {
throw new Error(`EnsIpfsResolver - no resolver found for name "${name}"`)
}
const Resolver = contract(resolverAbi).at(resolverAddress)
// lookup content id
const contentLookupResult = await Resolver.content(hash)
const contentHash = contentLookupResult[0]
if (hexValueIsEmpty(contentHash)) {
throw new Error(`EnsIpfsResolver - no content ID found for name "${name}"`)
}
const nonPrefixedHex = contentHash.slice(2)
const buffer = multihash.fromHexString(nonPrefixedHex)
const contentId = multihash.toB58String(multihash.encode(buffer, 'sha2-256'))
return contentId
}
function hexValueIsEmpty(value) {
return [undefined, null, '0x', '0x0', '0x0000000000000000000000000000000000000000000000000000000000000000'].includes(value)
}
function getRegistrarForChainId (chainId) {
switch (chainId) {
// mainnet
case 1:
return '0x314159265dd8dbb310642f98f50c066173c1259b'
// ropsten
case 3:
return '0x112234455c3a32fd11230c42e7bccd4a84e02010'
}
}

@ -0,0 +1,63 @@
const urlUtil = require('url')
const extension = require('extensionizer')
const resolveEnsToIpfsContentId = require('./resolver.js')
const supportedTopLevelDomains = ['eth']
module.exports = setupEnsIpfsResolver
function setupEnsIpfsResolver({ provider }) {
// install listener
const urlPatterns = supportedTopLevelDomains.map(tld => `*://*.${tld}/*`)
extension.webRequest.onErrorOccurred.addListener(webRequestDidFail, { urls: urlPatterns })
// return api object
return {
// uninstall listener
remove () {
extension.webRequest.onErrorOccurred.removeListener(webRequestDidFail)
},
}
async function webRequestDidFail (details) {
const { tabId, url } = details
// ignore requests that are not associated with tabs
if (tabId === -1) return
// parse ens name
const urlData = urlUtil.parse(url)
const { hostname: name, path, search } = urlData
const domainParts = name.split('.')
const topLevelDomain = domainParts[domainParts.length - 1]
// if unsupported TLD, abort
if (!supportedTopLevelDomains.includes(topLevelDomain)) return
// otherwise attempt resolve
attemptResolve({ tabId, name, path, search })
}
async function attemptResolve({ tabId, name, path, search }) {
extension.tabs.update(tabId, { url: `loading.html` })
try {
const ipfsContentId = await resolveEnsToIpfsContentId({ provider, name })
let url = `https://gateway.ipfs.io/ipfs/${ipfsContentId}${path}${search || ''}`
try {
// check if ipfs gateway has result
const response = await fetch(url, { method: 'HEAD' })
// if failure, redirect to 404 page
if (response.status !== 200) {
extension.tabs.update(tabId, { url: '404.html' })
return
}
// otherwise redirect to the correct page
extension.tabs.update(tabId, { url })
} catch (err) {
console.warn(err)
// if HEAD fetch failed, redirect so user can see relevant error page
extension.tabs.update(tabId, { url })
}
} catch (err) {
console.warn(err)
extension.tabs.update(tabId, { url: `error.html?name=${name}` })
}
}
}

@ -1,46 +0,0 @@
const extension = require('extensionizer')
const resolver = require('./resolver.js')
module.exports = function (provider) {
function ipfsContent (details) {
const name = details.url.substring(7, details.url.length - 1)
let clearTime = null
if (/^.+\.eth$/.test(name) === false) return
extension.tabs.query({active: true}, tab => {
extension.tabs.update(tab.id, { url: 'loading.html' })
clearTime = setTimeout(() => {
return extension.tabs.update(tab.id, { url: '404.html' })
}, 60000)
resolver.resolve(name, provider).then(ipfsHash => {
clearTimeout(clearTime)
let url = 'https://ipfs.infura.io/ipfs/' + ipfsHash
return fetch(url, { method: 'HEAD' }).then(response => response.status).then(statusCode => {
if (statusCode !== 200) return extension.tabs.update(tab.id, { url: '404.html' })
extension.tabs.update(tab.id, { url: url })
})
.catch(err => {
url = 'https://ipfs.infura.io/ipfs/' + ipfsHash
extension.tabs.update(tab.id, {url: url})
return err
})
})
.catch(err => {
clearTimeout(clearTime)
const url = err === 'unsupport' ? 'unsupport' : 'error'
extension.tabs.update(tab.id, {url: `${url}.html?name=${name}`})
})
})
return { cancel: true }
}
extension.webRequest.onErrorOccurred.addListener(ipfsContent, {urls: ['*://*.eth/'], types: ['main_frame']})
return {
remove () {
extension.webRequest.onErrorOccurred.removeListener(ipfsContent)
},
}
}

@ -1,71 +0,0 @@
const namehash = require('eth-ens-namehash')
const multihash = require('multihashes')
const HttpProvider = require('ethjs-provider-http')
const Eth = require('ethjs-query')
const EthContract = require('ethjs-contract')
const registrarAbi = require('./contracts/registrar')
const resolverAbi = require('./contracts/resolver')
function ens (name, provider) {
const eth = new Eth(new HttpProvider(getProvider(provider.type)))
const hash = namehash.hash(name)
const contract = new EthContract(eth)
const Registrar = contract(registrarAbi).at(getRegistrar(provider.type))
return new Promise((resolve, reject) => {
if (provider.type === 'mainnet' || provider.type === 'ropsten') {
Registrar.resolver(hash).then((address) => {
if (address === '0x0000000000000000000000000000000000000000') {
reject(null)
} else {
const Resolver = contract(resolverAbi).at(address['0'])
return Resolver.content(hash)
}
}).then((contentHash) => {
if (contentHash['0'] === '0x0000000000000000000000000000000000000000000000000000000000000000') reject(null)
if (contentHash.ret !== '0x') {
const hex = contentHash['0'].substring(2)
const buf = multihash.fromHexString(hex)
resolve(multihash.toB58String(multihash.encode(buf, 'sha2-256')))
} else {
reject(null)
}
})
} else {
return reject('unsupport')
}
})
}
function getProvider (type) {
switch (type) {
case 'mainnet':
return 'https://mainnet.infura.io/'
case 'ropsten':
return 'https://ropsten.infura.io/'
default:
return 'http://localhost:8545/'
}
}
function getRegistrar (type) {
switch (type) {
case 'mainnet':
return '0x314159265dd8dbb310642f98f50c066173c1259b'
case 'ropsten':
return '0x112234455c3a32fd11230c42e7bccd4a84e02010'
default:
return '0x0000000000000000000000000000000000000000'
}
}
module.exports.resolve = function (name, provider) {
const path = name.split('.')
const topLevelDomain = path[path.length - 1]
if (topLevelDomain === 'eth' || topLevelDomain === 'test') {
return ens(name, provider)
} else {
return new Promise((resolve, reject) => {
reject(null)
})
}
}

@ -197,6 +197,8 @@ module.exports = class MetamaskController extends EventEmitter {
}) })
this.networkController.on('networkDidChange', () => { this.networkController.on('networkDidChange', () => {
this.balancesController.updateAllBalances() this.balancesController.updateAllBalances()
var currentCurrency = this.currencyController.getCurrentCurrency()
this.setCurrentCurrency(currentCurrency, function() {})
}) })
this.balancesController.updateAllBalances() this.balancesController.updateAllBalances()
@ -1412,10 +1414,13 @@ module.exports = class MetamaskController extends EventEmitter {
* @param {Function} cb - A callback function returning currency info. * @param {Function} cb - A callback function returning currency info.
*/ */
setCurrentCurrency (currencyCode, cb) { setCurrentCurrency (currencyCode, cb) {
const { ticker } = this.networkController.getNetworkConfig()
try { try {
this.currencyController.setNativeCurrency(ticker)
this.currencyController.setCurrentCurrency(currencyCode) this.currencyController.setCurrentCurrency(currencyCode)
this.currencyController.updateConversionRate() this.currencyController.updateConversionRate()
const data = { const data = {
nativeCurrency: ticker || 'ETH',
conversionRate: this.currencyController.getConversionRate(), conversionRate: this.currencyController.getConversionRate(),
currentCurrency: this.currencyController.getCurrentCurrency(), currentCurrency: this.currencyController.getCurrentCurrency(),
conversionDate: this.currencyController.getConversionDate(), conversionDate: this.currencyController.getConversionDate(),
@ -1454,11 +1459,14 @@ module.exports = class MetamaskController extends EventEmitter {
/** /**
* A method for selecting a custom URL for an ethereum RPC provider. * A method for selecting a custom URL for an ethereum RPC provider.
* @param {string} rpcTarget - A URL for a valid Ethereum RPC API. * @param {string} rpcTarget - A URL for a valid Ethereum RPC API.
* @param {number} chainId - The chainId of the selected network.
* @param {string} ticker - The ticker symbol of the selected network.
* @param {string} nickname - Optional nickname of the selected network.
* @returns {Promise<String>} - The RPC Target URL confirmed. * @returns {Promise<String>} - The RPC Target URL confirmed.
*/ */
async setCustomRpc (rpcTarget) { async setCustomRpc (rpcTarget, chainId, ticker = 'ETH', nickname = '') {
this.networkController.setRpcTarget(rpcTarget) this.networkController.setRpcTarget(rpcTarget, chainId, ticker, nickname)
await this.preferencesController.updateFrequentRpcList(rpcTarget) await this.preferencesController.addToFrequentRpcList(rpcTarget, chainId, ticker, nickname)
return rpcTarget return rpcTarget
} }
@ -1467,7 +1475,7 @@ module.exports = class MetamaskController extends EventEmitter {
* @param {string} rpcTarget - A RPC URL to delete. * @param {string} rpcTarget - A RPC URL to delete.
*/ */
async delCustomRpc (rpcTarget) { async delCustomRpc (rpcTarget) {
await this.preferencesController.updateFrequentRpcList(rpcTarget, true) await this.preferencesController.removeFromFrequentRpcList(rpcTarget)
} }
/** /**

@ -109,7 +109,7 @@
}, },
"currentLocale": "en", "currentLocale": "en",
"preferences": { "preferences": {
"useETHAsPrimaryCurrency": true "useNativeCurrencyAsPrimaryCurrency": true
} }
}, },
"appState": { "appState": {

@ -152,7 +152,7 @@
}, },
"currentLocale": "en", "currentLocale": "en",
"preferences": { "preferences": {
"useETHAsPrimaryCurrency": true "useNativeCurrencyAsPrimaryCurrency": true
} }
}, },
"appState": { "appState": {

@ -110,7 +110,7 @@
}, },
"currentLocale": "en", "currentLocale": "en",
"preferences": { "preferences": {
"useETHAsPrimaryCurrency": true "useNativeCurrencyAsPrimaryCurrency": true
} }
}, },
"appState": { "appState": {

@ -39,7 +39,7 @@
"tokens": [], "tokens": [],
"currentLocale": "en", "currentLocale": "en",
"preferences": { "preferences": {
"useETHAsPrimaryCurrency": true "useNativeCurrencyAsPrimaryCurrency": true
} }
}, },
"appState": { "appState": {

@ -111,7 +111,7 @@
}, },
"currentLocale": "en", "currentLocale": "en",
"preferences": { "preferences": {
"useETHAsPrimaryCurrency": true "useNativeCurrencyAsPrimaryCurrency": true
} }
}, },
"appState": { "appState": {

@ -104,7 +104,7 @@
"send": {}, "send": {},
"currentLocale": "en", "currentLocale": "en",
"preferences": { "preferences": {
"useETHAsPrimaryCurrency": true "useNativeCurrencyAsPrimaryCurrency": true
} }
}, },
"appState": { "appState": {

@ -73,7 +73,7 @@ function mapStateToProps (state) {
forgottenPassword: state.appState.forgottenPassword, forgottenPassword: state.appState.forgottenPassword,
nextUnreadNotice: state.metamask.nextUnreadNotice, nextUnreadNotice: state.metamask.nextUnreadNotice,
lostAccounts: state.metamask.lostAccounts, lostAccounts: state.metamask.lostAccounts,
frequentRpcList: state.metamask.frequentRpcList || [], frequentRpcListDetail: state.metamask.frequentRpcListDetail || [],
featureFlags, featureFlags,
suggestedTokens: state.metamask.suggestedTokens, suggestedTokens: state.metamask.suggestedTokens,

@ -17,7 +17,7 @@ module.exports = class AppBar extends Component {
static propTypes = { static propTypes = {
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
frequentRpcList: PropTypes.array.isRequired, frequentRpcListDetail: PropTypes.array.isRequired,
isMascara: PropTypes.bool.isRequired, isMascara: PropTypes.bool.isRequired,
isOnboarding: PropTypes.bool.isRequired, isOnboarding: PropTypes.bool.isRequired,
identities: PropTypes.any.isRequired, identities: PropTypes.any.isRequired,
@ -196,7 +196,7 @@ module.exports = class AppBar extends Component {
renderNetworkDropdown () { renderNetworkDropdown () {
const { const {
dispatch, dispatch,
frequentRpcList: rpcList, frequentRpcListDetail: rpcList,
provider, provider,
} = this.props } = this.props
const { const {
@ -321,8 +321,8 @@ module.exports = class AppBar extends Component {
]) ])
} }
renderCustomOption ({ rpcTarget, type }) { renderCustomOption ({ rpcTarget, type, ticker }) {
const {dispatch} = this.props const {dispatch, network} = this.props
if (type !== 'rpc') { if (type !== 'rpc') {
return null return null
@ -340,7 +340,7 @@ module.exports = class AppBar extends Component {
default: default:
return h(DropdownMenuItem, { return h(DropdownMenuItem, {
key: rpcTarget, key: rpcTarget,
onClick: () => dispatch(actions.setRpcTarget(rpcTarget)), onClick: () => dispatch(actions.setRpcTarget(rpcTarget, network, ticker)),
closeMenu: () => this.setState({ isNetworkMenuOpen: false }), closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
}, [ }, [
h('i.fa.fa-question-circle.fa-lg.menu-icon'), h('i.fa.fa-question-circle.fa-lg.menu-icon'),
@ -354,7 +354,8 @@ module.exports = class AppBar extends Component {
const {dispatch} = this.props const {dispatch} = this.props
const reversedRpcList = rpcList.slice().reverse() const reversedRpcList = rpcList.slice().reverse()
return reversedRpcList.map((rpc) => { return reversedRpcList.map((entry) => {
const rpc = entry.rpcUrl
const currentRpcTarget = provider.type === 'rpc' && rpc === provider.rpcTarget const currentRpcTarget = provider.type === 'rpc' && rpc === provider.rpcTarget
if ((rpc === LOCALHOST_RPC_URL) || currentRpcTarget) { if ((rpc === LOCALHOST_RPC_URL) || currentRpcTarget) {
@ -363,7 +364,7 @@ module.exports = class AppBar extends Component {
return h(DropdownMenuItem, { return h(DropdownMenuItem, {
key: `common${rpc}`, key: `common${rpc}`,
closeMenu: () => this.setState({ isNetworkMenuOpen: false }), closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
onClick: () => dispatch(actions.setRpcTarget(rpc)), onClick: () => dispatch(actions.setRpcTarget(rpc, entry.chainId, entry.ticker)),
}, [ }, [
h('i.fa.fa-question-circle.fa-lg.menu-icon'), h('i.fa.fa-question-circle.fa-lg.menu-icon'),
rpc, rpc,

@ -1,12 +1,18 @@
const Component = require('react').Component const Component = require('react').Component
const h = require('react-hyperscript') const h = require('react-hyperscript')
const connect = require('react-redux').connect
const inherits = require('util').inherits const inherits = require('util').inherits
const formatBalance = require('../util').formatBalance const formatBalance = require('../util').formatBalance
const generateBalanceObject = require('../util').generateBalanceObject const generateBalanceObject = require('../util').generateBalanceObject
const Tooltip = require('./tooltip.js') const Tooltip = require('./tooltip.js')
const FiatValue = require('./fiat-value.js') const FiatValue = require('./fiat-value.js')
module.exports = EthBalanceComponent module.exports = connect(mapStateToProps)(EthBalanceComponent)
function mapStateToProps (state) {
return {
ticker: state.metamask.ticker,
}
}
inherits(EthBalanceComponent, Component) inherits(EthBalanceComponent, Component)
function EthBalanceComponent () { function EthBalanceComponent () {
@ -16,9 +22,10 @@ function EthBalanceComponent () {
EthBalanceComponent.prototype.render = function () { EthBalanceComponent.prototype.render = function () {
var props = this.props var props = this.props
let { value } = props let { value } = props
const { ticker } = props
var style = props.style var style = props.style
var needsParse = this.props.needsParse !== undefined ? this.props.needsParse : true var needsParse = this.props.needsParse !== undefined ? this.props.needsParse : true
value = value ? formatBalance(value, 6, needsParse) : '...' value = value ? formatBalance(value, 6, needsParse, ticker) : '...'
var width = props.width var width = props.width
return ( return (

@ -1,12 +1,18 @@
const Component = require('react').Component const Component = require('react').Component
const h = require('react-hyperscript') const h = require('react-hyperscript')
const connect = require('react-redux').connect
const inherits = require('util').inherits const inherits = require('util').inherits
const formatBalance = require('../util').formatBalance const formatBalance = require('../util').formatBalance
const generateBalanceObject = require('../util').generateBalanceObject const generateBalanceObject = require('../util').generateBalanceObject
const Tooltip = require('./tooltip.js') const Tooltip = require('./tooltip.js')
const FiatValue = require('./fiat-value.js') const FiatValue = require('./fiat-value.js')
module.exports = EthBalanceComponent module.exports = connect(mapStateToProps)(EthBalanceComponent)
function mapStateToProps (state) {
return {
ticker: state.metamask.ticker,
}
}
inherits(EthBalanceComponent, Component) inherits(EthBalanceComponent, Component)
function EthBalanceComponent () { function EthBalanceComponent () {
@ -16,9 +22,9 @@ function EthBalanceComponent () {
EthBalanceComponent.prototype.render = function () { EthBalanceComponent.prototype.render = function () {
var props = this.props var props = this.props
let { value } = props let { value } = props
const { style, width } = props const { ticker, style, width } = props
var needsParse = this.props.needsParse !== undefined ? this.props.needsParse : true var needsParse = this.props.needsParse !== undefined ? this.props.needsParse : true
value = value ? formatBalance(value, 6, needsParse) : '...' value = value ? formatBalance(value, 6, needsParse, ticker) : '...'
return ( return (

@ -68,7 +68,7 @@ ConfigScreen.prototype.render = function () {
currentProviderDisplay(metamaskState), currentProviderDisplay(metamaskState),
h('div', { style: {display: 'flex'} }, [ h('div', { style: {display: 'block'} }, [
h('input#new_rpc', { h('input#new_rpc', {
placeholder: 'New RPC URL', placeholder: 'New RPC URL',
style: { style: {
@ -81,7 +81,70 @@ ConfigScreen.prototype.render = function () {
if (event.key === 'Enter') { if (event.key === 'Enter') {
var element = event.target var element = event.target
var newRpc = element.value var newRpc = element.value
rpcValidation(newRpc, state) var chainid = document.querySelector('input#chainid')
var ticker = document.querySelector('input#ticker')
var nickname = document.querySelector('input#nickname')
rpcValidation(newRpc, chainid.value, ticker.value, nickname.value, state)
}
},
}),
h('br'),
h('input#chainid', {
placeholder: 'ChainId (optional)',
style: {
width: 'inherit',
flex: '1 0 auto',
height: '30px',
margin: '8px',
},
onKeyPress (event) {
if (event.key === 'Enter') {
var element = document.querySelector('input#new_rpc')
var newRpc = element.value
var chainid = document.querySelector('input#chainid')
var ticker = document.querySelector('input#ticker')
var nickname = document.querySelector('input#nickname')
rpcValidation(newRpc, chainid.value, ticker.value, nickname.value, state)
}
},
}),
h('br'),
h('input#ticker', {
placeholder: 'Symbol (optional)',
style: {
width: 'inherit',
flex: '1 0 auto',
height: '30px',
margin: '8px',
},
onKeyPress (event) {
if (event.key === 'Enter') {
var element = document.querySelector('input#new_rpc')
var newRpc = element.value
var chainid = document.querySelector('input#chainid')
var ticker = document.querySelector('input#ticker')
var nickname = document.querySelector('input#nickname')
rpcValidation(newRpc, chainid.value, ticker.value, nickname.value, state)
}
},
}),
h('br'),
h('input#nickname', {
placeholder: 'Nickname (optional)',
style: {
width: 'inherit',
flex: '1 0 auto',
height: '30px',
margin: '8px',
},
onKeyPress (event) {
if (event.key === 'Enter') {
var element = document.querySelector('input#new_rpc')
var newRpc = element.value
var chainid = document.querySelector('input#chainid')
var ticker = document.querySelector('input#ticker')
var nickname = document.querySelector('input#nickname')
rpcValidation(newRpc, chainid.value, ticker.value, nickname.value, state)
} }
}, },
}), }),
@ -93,7 +156,10 @@ ConfigScreen.prototype.render = function () {
event.preventDefault() event.preventDefault()
var element = document.querySelector('input#new_rpc') var element = document.querySelector('input#new_rpc')
var newRpc = element.value var newRpc = element.value
rpcValidation(newRpc, state) var chainid = document.querySelector('input#chainid')
var ticker = document.querySelector('input#ticker')
var nickname = document.querySelector('input#nickname')
rpcValidation(newRpc, chainid.value, ticker.value, nickname.value, state)
}, },
}, 'Save'), }, 'Save'),
]), ]),
@ -189,9 +255,9 @@ ConfigScreen.prototype.render = function () {
) )
} }
function rpcValidation (newRpc, state) { function rpcValidation (newRpc, chainid, ticker = 'ETH', nickname = '', state) {
if (validUrl.isWebUri(newRpc)) { if (validUrl.isWebUri(newRpc)) {
state.dispatch(actions.setRpcTarget(newRpc)) state.dispatch(actions.setRpcTarget(newRpc, chainid, ticker, nickname))
} else { } else {
var appendedRpc = `http://${newRpc}` var appendedRpc = `http://${newRpc}`
if (validUrl.isWebUri(appendedRpc)) { if (validUrl.isWebUri(appendedRpc)) {

@ -102,7 +102,7 @@ function parseBalance (balance) {
// Takes wei hex, returns an object with three properties. // Takes wei hex, returns an object with three properties.
// Its "formatted" property is what we generally use to render values. // Its "formatted" property is what we generally use to render values.
function formatBalance (balance, decimalsToKeep, needsParse = true) { function formatBalance (balance, decimalsToKeep, needsParse = true, ticker = 'ETH') {
var parsed = needsParse ? parseBalance(balance) : balance.split('.') var parsed = needsParse ? parseBalance(balance) : balance.split('.')
var beforeDecimal = parsed[0] var beforeDecimal = parsed[0]
var afterDecimal = parsed[1] var afterDecimal = parsed[1]
@ -112,14 +112,14 @@ function formatBalance (balance, decimalsToKeep, needsParse = true) {
if (afterDecimal !== '0') { if (afterDecimal !== '0') {
var sigFigs = afterDecimal.match(/^0*(.{2})/) // default: grabs 2 most significant digits var sigFigs = afterDecimal.match(/^0*(.{2})/) // default: grabs 2 most significant digits
if (sigFigs) { afterDecimal = sigFigs[0] } if (sigFigs) { afterDecimal = sigFigs[0] }
formatted = '0.' + afterDecimal + ' ETH' formatted = '0.' + afterDecimal + ` ${ticker}`
} }
} else { } else {
formatted = beforeDecimal + '.' + afterDecimal.slice(0, 3) + ' ETH' formatted = beforeDecimal + '.' + afterDecimal.slice(0, 3) + ` ${ticker}`
} }
} else { } else {
afterDecimal += Array(decimalsToKeep).join('0') afterDecimal += Array(decimalsToKeep).join('0')
formatted = beforeDecimal + '.' + afterDecimal.slice(0, decimalsToKeep) + ' ETH' formatted = beforeDecimal + '.' + afterDecimal.slice(0, decimalsToKeep) + ` ${ticker}`
} }
return formatted return formatted
} }

402
package-lock.json generated

@ -9570,9 +9570,9 @@
} }
}, },
"eth-block-tracker": { "eth-block-tracker": {
"version": "4.0.3", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/eth-block-tracker/-/eth-block-tracker-4.0.3.tgz", "resolved": "https://registry.npmjs.org/eth-block-tracker/-/eth-block-tracker-4.1.0.tgz",
"integrity": "sha512-Uy+5hEvOT1/C6N1Lw/uQ10v03ArnNEQEkM0yhJQWwpd8Ymy3sw4jk75SE58s1spfOBBtnr8JaSAFioAFSeg6HA==", "integrity": "sha512-991xTy6CzYYbizkHmgRFFI9iGx1OCISve8sSLuOlt7/yD7VFH1Jd8mOmBqxaG5ywGkIXdwAR78nQ2WDReETzBg==",
"requires": { "requires": {
"eth-json-rpc-infura": "^3.1.2", "eth-json-rpc-infura": "^3.1.2",
"eth-query": "^2.1.0", "eth-query": "^2.1.0",
@ -9604,7 +9604,7 @@
}, },
"eth-json-rpc-middleware": { "eth-json-rpc-middleware": {
"version": "1.6.0", "version": "1.6.0",
"resolved": "https://registry.npmjs.org/eth-json-rpc-middleware/-/eth-json-rpc-middleware-1.6.0.tgz", "resolved": "http://registry.npmjs.org/eth-json-rpc-middleware/-/eth-json-rpc-middleware-1.6.0.tgz",
"integrity": "sha512-tDVCTlrUvdqHKqivYMjtFZsdD7TtpNLBCfKAcOpaVs7orBMS/A8HWro6dIzNtTZIR05FAbJ3bioFOnZpuCew9Q==", "integrity": "sha512-tDVCTlrUvdqHKqivYMjtFZsdD7TtpNLBCfKAcOpaVs7orBMS/A8HWro6dIzNtTZIR05FAbJ3bioFOnZpuCew9Q==",
"requires": { "requires": {
"async": "^2.5.0", "async": "^2.5.0",
@ -9643,12 +9643,12 @@
}, },
"node-fetch": { "node-fetch": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz", "resolved": "http://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz",
"integrity": "sha1-q4hOjn5X44qUR1POxwb3iNF2i7U=" "integrity": "sha1-q4hOjn5X44qUR1POxwb3iNF2i7U="
}, },
"whatwg-fetch": { "whatwg-fetch": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", "resolved": "http://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz",
"integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==" "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng=="
} }
} }
@ -9932,12 +9932,22 @@
"requires": { "requires": {
"ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7", "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
"ethereumjs-util": "^5.1.1" "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",
"dev": true,
"requires": {
"bn.js": "^4.10.0",
"ethereumjs-util": "^5.0.0"
}
}
} }
}, },
"ethereumjs-abi": { "ethereumjs-abi": {
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7", "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git", "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"dev": true,
"requires": { "requires": {
"bn.js": "^4.10.0", "bn.js": "^4.10.0",
"ethereumjs-util": "^5.0.0" "ethereumjs-util": "^5.0.0"
@ -9947,7 +9957,6 @@
"version": "5.2.0", "version": "5.2.0",
"resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz", "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz",
"integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==", "integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==",
"dev": true,
"requires": { "requires": {
"bn.js": "^4.11.0", "bn.js": "^4.11.0",
"create-hash": "^1.1.2", "create-hash": "^1.1.2",
@ -10390,7 +10399,7 @@
"dependencies": { "dependencies": {
"babelify": { "babelify": {
"version": "7.3.0", "version": "7.3.0",
"resolved": "http://registry.npmjs.org/babelify/-/babelify-7.3.0.tgz", "resolved": "https://registry.npmjs.org/babelify/-/babelify-7.3.0.tgz",
"integrity": "sha1-qlau3nBn/XvVSWZu4W3ChQh+iOU=", "integrity": "sha1-qlau3nBn/XvVSWZu4W3ChQh+iOU=",
"requires": { "requires": {
"babel-core": "^6.0.14", "babel-core": "^6.0.14",
@ -14249,14 +14258,354 @@
"dev": true "dev": true
}, },
"glob-watcher": { "glob-watcher": {
"version": "4.0.0", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-4.0.0.tgz", "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.1.tgz",
"integrity": "sha1-nmOo/25h6TLebMLK7OUHGm1zcyk=", "integrity": "sha512-fK92r2COMC199WCyGUblrZKhjra3cyVMDiypDdqg1vsSDmexnbYivK1kNR4QItiNXLKmGlqan469ks67RtNa2g==",
"requires": { "requires": {
"async-done": "^1.2.0", "async-done": "^1.2.0",
"chokidar": "^1.4.3", "chokidar": "^2.0.0",
"just-debounce": "^1.0.0", "just-debounce": "^1.0.0",
"object.defaults": "^1.1.0" "object.defaults": "^1.1.0"
},
"dependencies": {
"anymatch": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
"integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==",
"requires": {
"micromatch": "^3.1.4",
"normalize-path": "^2.1.1"
}
},
"arr-diff": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
"integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA="
},
"array-unique": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
"integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg="
},
"braces": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
"integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
"requires": {
"arr-flatten": "^1.1.0",
"array-unique": "^0.3.2",
"extend-shallow": "^2.0.1",
"fill-range": "^4.0.0",
"isobject": "^3.0.1",
"repeat-element": "^1.1.2",
"snapdragon": "^0.8.1",
"snapdragon-node": "^2.0.1",
"split-string": "^3.0.2",
"to-regex": "^3.0.1"
},
"dependencies": {
"extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"requires": {
"is-extendable": "^0.1.0"
}
}
}
},
"chokidar": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz",
"integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==",
"requires": {
"anymatch": "^2.0.0",
"async-each": "^1.0.0",
"braces": "^2.3.0",
"fsevents": "^1.2.2",
"glob-parent": "^3.1.0",
"inherits": "^2.0.1",
"is-binary-path": "^1.0.0",
"is-glob": "^4.0.0",
"lodash.debounce": "^4.0.8",
"normalize-path": "^2.1.1",
"path-is-absolute": "^1.0.0",
"readdirp": "^2.0.0",
"upath": "^1.0.5"
}
},
"define-property": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
"integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
"requires": {
"is-descriptor": "^1.0.2",
"isobject": "^3.0.1"
}
},
"expand-brackets": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
"integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
"requires": {
"debug": "^2.3.3",
"define-property": "^0.2.5",
"extend-shallow": "^2.0.1",
"posix-character-classes": "^0.1.0",
"regex-not": "^1.0.0",
"snapdragon": "^0.8.1",
"to-regex": "^3.0.1"
},
"dependencies": {
"define-property": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
"integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
"requires": {
"is-descriptor": "^0.1.0"
}
},
"extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"requires": {
"is-extendable": "^0.1.0"
}
},
"is-descriptor": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
"integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
"requires": {
"is-accessor-descriptor": "^0.1.6",
"is-data-descriptor": "^0.1.4",
"kind-of": "^5.0.0"
}
},
"kind-of": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
"integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw=="
}
}
},
"extend-shallow": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
"integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
"requires": {
"assign-symbols": "^1.0.0",
"is-extendable": "^1.0.1"
},
"dependencies": {
"is-extendable": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
"integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
"requires": {
"is-plain-object": "^2.0.4"
}
}
}
},
"extglob": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
"integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
"requires": {
"array-unique": "^0.3.2",
"define-property": "^1.0.0",
"expand-brackets": "^2.1.4",
"extend-shallow": "^2.0.1",
"fragment-cache": "^0.2.1",
"regex-not": "^1.0.0",
"snapdragon": "^0.8.1",
"to-regex": "^3.0.1"
},
"dependencies": {
"define-property": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
"integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
"requires": {
"is-descriptor": "^1.0.0"
}
},
"extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"requires": {
"is-extendable": "^0.1.0"
}
}
}
},
"fill-range": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
"integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
"requires": {
"extend-shallow": "^2.0.1",
"is-number": "^3.0.0",
"repeat-string": "^1.6.1",
"to-regex-range": "^2.1.0"
},
"dependencies": {
"extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"requires": {
"is-extendable": "^0.1.0"
}
}
}
},
"glob-parent": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
"integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
"requires": {
"is-glob": "^3.1.0",
"path-dirname": "^1.0.0"
},
"dependencies": {
"is-glob": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
"integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
"requires": {
"is-extglob": "^2.1.0"
}
}
}
},
"is-accessor-descriptor": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
"integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
"requires": {
"kind-of": "^3.0.2"
},
"dependencies": {
"kind-of": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
"requires": {
"is-buffer": "^1.1.5"
}
}
}
},
"is-data-descriptor": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
"integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
"requires": {
"kind-of": "^3.0.2"
},
"dependencies": {
"kind-of": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
"requires": {
"is-buffer": "^1.1.5"
}
}
}
},
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI="
},
"is-glob": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz",
"integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=",
"requires": {
"is-extglob": "^2.1.1"
}
},
"is-number": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
"integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
"requires": {
"kind-of": "^3.0.2"
},
"dependencies": {
"kind-of": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
"requires": {
"is-buffer": "^1.1.5"
}
}
}
},
"isobject": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
},
"kind-of": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
"integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA=="
},
"micromatch": {
"version": "3.1.10",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
"integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
"requires": {
"arr-diff": "^4.0.0",
"array-unique": "^0.3.2",
"braces": "^2.3.1",
"define-property": "^2.0.2",
"extend-shallow": "^3.0.2",
"extglob": "^2.0.4",
"fragment-cache": "^0.2.1",
"kind-of": "^6.0.2",
"nanomatch": "^1.2.9",
"object.pick": "^1.3.0",
"regex-not": "^1.0.0",
"snapdragon": "^0.8.1",
"to-regex": "^3.0.2"
}
},
"to-regex": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz",
"integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==",
"requires": {
"define-property": "^2.0.2",
"extend-shallow": "^3.0.2",
"regex-not": "^1.0.2",
"safe-regex": "^1.1.0"
},
"dependencies": {
"regex-not": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
"integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==",
"requires": {
"extend-shallow": "^3.0.2",
"safe-regex": "^1.1.0"
}
}
}
},
"upath": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz",
"integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw=="
}
} }
}, },
"glob2base": { "glob2base": {
@ -14381,10 +14730,10 @@
"dev": true "dev": true
}, },
"gulp": { "gulp": {
"version": "github:gulpjs/gulp#71c094a51c7972d26f557899ddecab0210ef3776", "version": "github:gulpjs/gulp#55eb23a268dcc7340bb40808600fd4802848c06f",
"from": "github:gulpjs/gulp#4.0", "from": "github:gulpjs/gulp#v4.0.0",
"requires": { "requires": {
"glob-watcher": "^4.0.0", "glob-watcher": "^5.0.0",
"gulp-cli": "^2.0.0", "gulp-cli": "^2.0.0",
"undertaker": "^1.0.0", "undertaker": "^1.0.0",
"vinyl-fs": "^3.0.0" "vinyl-fs": "^3.0.0"
@ -21494,9 +21843,9 @@
"dev": true "dev": true
}, },
"mute-stdout": { "mute-stdout": {
"version": "1.0.0", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.0.tgz", "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz",
"integrity": "sha1-WzLqB+tDyd7WEwQ0z5JvRrKn/U0=" "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg=="
}, },
"mute-stream": { "mute-stream": {
"version": "0.0.7", "version": "0.0.7",
@ -33471,9 +33820,9 @@
"optional": true "optional": true
}, },
"v8flags": { "v8flags": {
"version": "3.1.0", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.1.0.tgz", "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.1.1.tgz",
"integrity": "sha512-0m69VIK2dudEf2Ub0xwLQhZkDZu85OmiOpTw+UGDt56ibviYICHziM/3aE+oVg7IjGPp0c83w3eSVqa+lYZ9UQ==", "integrity": "sha512-iw/1ViSEaff8NJ3HLyEjawk/8hjJib3E7pvG4pddVXfUg1983s3VGsiClDjhK64MQVDGqc1Q8r18S4VKQZS9EQ==",
"requires": { "requires": {
"homedir-polyfill": "^1.0.1" "homedir-polyfill": "^1.0.1"
} }
@ -33700,9 +34049,12 @@
}, },
"dependencies": { "dependencies": {
"convert-source-map": { "convert-source-map": {
"version": "1.5.1", "version": "1.6.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz",
"integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=" "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==",
"requires": {
"safe-buffer": "~5.1.1"
}
} }
} }
}, },

@ -112,7 +112,7 @@
"ensnare": "^1.0.0", "ensnare": "^1.0.0",
"eslint-plugin-react": "^7.4.0", "eslint-plugin-react": "^7.4.0",
"eth-bin-to-ops": "^1.0.1", "eth-bin-to-ops": "^1.0.1",
"eth-block-tracker": "^4.0.3", "eth-block-tracker": "^4.1.0",
"eth-contract-metadata": "github:MetaMask/eth-contract-metadata#master", "eth-contract-metadata": "github:MetaMask/eth-contract-metadata#master",
"eth-ens-namehash": "^2.0.8", "eth-ens-namehash": "^2.0.8",
"eth-hd-keyring": "^1.2.2", "eth-hd-keyring": "^1.2.2",
@ -143,7 +143,7 @@
"fast-levenshtein": "^2.0.6", "fast-levenshtein": "^2.0.6",
"file-loader": "^1.1.11", "file-loader": "^1.1.11",
"fuse.js": "^3.2.0", "fuse.js": "^3.2.0",
"gulp": "github:gulpjs/gulp#4.0", "gulp": "github:gulpjs/gulp#v4.0.0",
"gulp-autoprefixer": "^5.0.0", "gulp-autoprefixer": "^5.0.0",
"gulp-debug": "^3.2.0", "gulp-debug": "^3.2.0",
"gulp-eslint": "^4.0.0", "gulp-eslint": "^4.0.0",

@ -111,7 +111,9 @@
"0x108cf70c7d384c552f42c07c41c0e1e46d77ea0d": 0.00039345803819379796, "0x108cf70c7d384c552f42c07c41c0e1e46d77ea0d": 0.00039345803819379796,
"0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5": 0.00008189274407698049 "0xd8f6a2ffb0fc5952d16c9768b71cfd35b6399aa5": 0.00008189274407698049
}, },
"ticker": "ETH",
"currentCurrency": "usd", "currentCurrency": "usd",
"nativeCurrency": "ETH",
"conversionRate": 556.12, "conversionRate": 556.12,
"addressBook": [ "addressBook": [
{ {

@ -319,7 +319,7 @@ describe('Using MetaMask with an existing account', function () {
const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary')) const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
assert.equal(txValues.length, 1) assert.equal(txValues.length, 1)
assert.equal(await txValues[0].getText(), '-1 ETH') assert.ok(/-1\s*ETH/.test(await txValues[0].getText()))
}) })
}) })

@ -371,7 +371,7 @@ describe('MetaMask', function () {
it('balance renders', async () => { it('balance renders', async () => {
const balance = await findElement(driver, By.css('.balance-display .token-amount')) const balance = await findElement(driver, By.css('.balance-display .token-amount'))
await driver.wait(until.elementTextMatches(balance, /100.+ETH/)) await driver.wait(until.elementTextMatches(balance, /100\s*ETH/))
await delay(regularDelayMs) await delay(regularDelayMs)
}) })
}) })
@ -420,7 +420,7 @@ describe('MetaMask', function () {
if (process.env.SELENIUM_BROWSER !== 'firefox') { if (process.env.SELENIUM_BROWSER !== 'firefox') {
const txValues = await findElement(driver, By.css('.transaction-list-item__amount--primary')) const txValues = await findElement(driver, By.css('.transaction-list-item__amount--primary'))
await driver.wait(until.elementTextMatches(txValues, /-1\sETH/), 10000) await driver.wait(until.elementTextMatches(txValues, /-1\s*ETH/), 10000)
} }
}) })
}) })
@ -462,7 +462,7 @@ describe('MetaMask', function () {
assert.equal(transactions.length, 2) assert.equal(transactions.length, 2)
const txValues = await findElement(driver, By.css('.transaction-list-item__amount--primary')) const txValues = await findElement(driver, By.css('.transaction-list-item__amount--primary'))
await driver.wait(until.elementTextMatches(txValues, /-3\sETH/), 10000) await driver.wait(until.elementTextMatches(txValues, /-3\s*ETH/), 10000)
}) })
}) })
@ -540,7 +540,7 @@ describe('MetaMask', function () {
await findElements(driver, By.css('.transaction-list-item')) await findElements(driver, By.css('.transaction-list-item'))
const [txListValue] = await findElements(driver, By.css('.transaction-list-item__amount--primary')) const [txListValue] = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
await driver.wait(until.elementTextMatches(txListValue, /-4\sETH/), 10000) await driver.wait(until.elementTextMatches(txListValue, /-4\s*ETH/), 10000)
await txListValue.click() await txListValue.click()
await delay(regularDelayMs) await delay(regularDelayMs)
@ -574,7 +574,7 @@ describe('MetaMask', function () {
}, 10000) }, 10000)
const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary')) const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
await driver.wait(until.elementTextMatches(txValues[0], /-4\sETH/), 10000) await driver.wait(until.elementTextMatches(txValues[0], /-4\s*ETH/), 10000)
// const txAccounts = await findElements(driver, By.css('.tx-list-account')) // const txAccounts = await findElements(driver, By.css('.tx-list-account'))
// const firstTxAddress = await txAccounts[0].getText() // const firstTxAddress = await txAccounts[0].getText()
@ -606,7 +606,7 @@ describe('MetaMask', function () {
}, 10000) }, 10000)
const txValues = await findElement(driver, By.css('.transaction-list-item__amount--primary')) const txValues = await findElement(driver, By.css('.transaction-list-item__amount--primary'))
await driver.wait(until.elementTextMatches(txValues, /-0\sETH/), 10000) await driver.wait(until.elementTextMatches(txValues, /-0\s*ETH/), 10000)
await closeAllWindowHandlesExcept(driver, [extension, dapp]) await closeAllWindowHandlesExcept(driver, [extension, dapp])
await driver.switchTo().window(extension) await driver.switchTo().window(extension)
@ -616,9 +616,9 @@ describe('MetaMask', function () {
const balance = await findElement(driver, By.css('.transaction-view-balance__primary-balance')) const balance = await findElement(driver, By.css('.transaction-view-balance__primary-balance'))
await delay(regularDelayMs) await delay(regularDelayMs)
if (process.env.SELENIUM_BROWSER !== 'firefox') { if (process.env.SELENIUM_BROWSER !== 'firefox') {
await driver.wait(until.elementTextMatches(balance, /^92.*ETH.*$/), 10000) await driver.wait(until.elementTextMatches(balance, /^92.*\s*ETH.*$/), 10000)
const tokenAmount = await balance.getText() const tokenAmount = await balance.getText()
assert.ok(/^92.*ETH.*$/.test(tokenAmount)) assert.ok(/^92.*\s*ETH.*$/.test(tokenAmount))
await delay(regularDelayMs) await delay(regularDelayMs)
} }
}) })
@ -764,7 +764,7 @@ describe('MetaMask', function () {
// test cancelled on firefox until https://github.com/mozilla/geckodriver/issues/906 is resolved, // test cancelled on firefox until https://github.com/mozilla/geckodriver/issues/906 is resolved,
// or possibly until we use latest version of firefox in the tests // or possibly until we use latest version of firefox in the tests
if (process.env.SELENIUM_BROWSER !== 'firefox') { if (process.env.SELENIUM_BROWSER !== 'firefox') {
await driver.wait(until.elementTextMatches(txValues[0], /-50\sTST/), 10000) await driver.wait(until.elementTextMatches(txValues[0], /-50\s*TST/), 10000)
} }
driver.wait(async () => { driver.wait(async () => {
@ -798,7 +798,7 @@ describe('MetaMask', function () {
await findElements(driver, By.css('.transaction-list__pending-transactions')) await findElements(driver, By.css('.transaction-list__pending-transactions'))
const [txListValue] = await findElements(driver, By.css('.transaction-list-item__amount--primary')) const [txListValue] = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
await driver.wait(until.elementTextMatches(txListValue, /-7\sTST/), 10000) await driver.wait(until.elementTextMatches(txListValue, /-7\s*TST/), 10000)
await txListValue.click() await txListValue.click()
await delay(regularDelayMs) await delay(regularDelayMs)
@ -851,7 +851,7 @@ describe('MetaMask', function () {
}, 10000) }, 10000)
const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary')) const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
await driver.wait(until.elementTextMatches(txValues[0], /-7\sTST/)) await driver.wait(until.elementTextMatches(txValues[0], /-7\s*TST/))
const txStatuses = await findElements(driver, By.css('.transaction-list-item__action')) const txStatuses = await findElements(driver, By.css('.transaction-list-item__action'))
await driver.wait(until.elementTextMatches(txStatuses[0], /Sent\sToken/)) await driver.wait(until.elementTextMatches(txStatuses[0], /Sent\sToken/))
@ -897,7 +897,7 @@ describe('MetaMask', function () {
const [txListItem] = await findElements(driver, By.css('.transaction-list-item')) const [txListItem] = await findElements(driver, By.css('.transaction-list-item'))
const [txListValue] = await findElements(driver, By.css('.transaction-list-item__amount--primary')) const [txListValue] = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
await driver.wait(until.elementTextMatches(txListValue, /-7\sTST/)) await driver.wait(until.elementTextMatches(txListValue, /-7\s*TST/))
await txListItem.click() await txListItem.click()
await delay(regularDelayMs) await delay(regularDelayMs)
}) })
@ -974,7 +974,7 @@ describe('MetaMask', function () {
}, 10000) }, 10000)
const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary')) const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
await driver.wait(until.elementTextMatches(txValues[0], /-7\sTST/)) await driver.wait(until.elementTextMatches(txValues[0], /-7\s*TST/))
const txStatuses = await findElements(driver, By.css('.transaction-list-item__action')) const txStatuses = await findElements(driver, By.css('.transaction-list-item__action'))
await driver.wait(until.elementTextMatches(txStatuses[0], /Approve/)) await driver.wait(until.elementTextMatches(txStatuses[0], /Approve/))
}) })
@ -1027,7 +1027,7 @@ describe('MetaMask', function () {
it('renders the balance for the chosen token', async () => { it('renders the balance for the chosen token', async () => {
const balance = await findElement(driver, By.css('.transaction-view-balance__token-balance')) const balance = await findElement(driver, By.css('.transaction-view-balance__token-balance'))
await driver.wait(until.elementTextMatches(balance, /0\sBAT/)) await driver.wait(until.elementTextMatches(balance, /0\s*BAT/))
await delay(regularDelayMs) await delay(regularDelayMs)
}) })
}) })

@ -11,7 +11,7 @@ sleep 5
cd test/e2e/beta/ cd test/e2e/beta/
rm -rf drizzle-test rm -rf drizzle-test
mkdir drizzle-test && cd drizzle-test mkdir drizzle-test && cd drizzle-test
npm install truffle sudo npm install -g truffle
truffle unbox drizzle truffle unbox drizzle
echo "Deploying contracts for Drizzle test..." echo "Deploying contracts for Drizzle test..."
truffle compile && truffle migrate truffle compile && truffle migrate

@ -47,7 +47,7 @@ describe('# Network Controller', function () {
describe('#setNetworkState', function () { describe('#setNetworkState', function () {
it('should update the network', function () { it('should update the network', function () {
networkController.setNetworkState(1) networkController.setNetworkState(1, 'rpc')
const networkState = networkController.getNetworkState() const networkState = networkController.getNetworkState()
assert.equal(networkState, 1, 'network is 1') assert.equal(networkState, 1, 'network is 1')
}) })

@ -375,6 +375,11 @@ describe('preferences controller', function () {
await preferencesController.requestWatchAsset(req, res, asy.next, asy.end) await preferencesController.requestWatchAsset(req, res, asy.next, asy.end)
sandbox.assert.called(stubEnd) sandbox.assert.called(stubEnd)
sandbox.assert.notCalled(stubNext) sandbox.assert.notCalled(stubNext)
req.method = 'wallet_watchAsset'
req.params.type = 'someasset'
await preferencesController.requestWatchAsset(req, res, asy.next, asy.end)
sandbox.assert.calledTwice(stubEnd)
sandbox.assert.notCalled(stubNext)
}) })
it('should through error if method is supported but asset type is not', async function () { it('should through error if method is supported but asset type is not', async function () {
req.method = 'metamask_watchAsset' req.method = 'metamask_watchAsset'
@ -479,5 +484,24 @@ describe('preferences controller', function () {
assert.equal(preferencesController.store.getState().seedWords, 'foo bar baz') assert.equal(preferencesController.store.getState().seedWords, 'foo bar baz')
}) })
}) })
describe('on updateFrequentRpcList', function () {
it('should add custom RPC url to state', function () {
preferencesController.addToFrequentRpcList('rpc_url', 1)
preferencesController.addToFrequentRpcList('http://localhost:8545', 1)
assert.deepEqual(preferencesController.store.getState().frequentRpcListDetail, [{ rpcUrl: 'rpc_url', chainId: 1, ticker: 'ETH', nickname: '' }] )
preferencesController.addToFrequentRpcList('rpc_url', 1)
assert.deepEqual(preferencesController.store.getState().frequentRpcListDetail, [{ rpcUrl: 'rpc_url', chainId: 1, ticker: 'ETH', nickname: '' }] )
})
it('should remove custom RPC url from state', function () {
preferencesController.addToFrequentRpcList('rpc_url', 1)
assert.deepEqual(preferencesController.store.getState().frequentRpcListDetail, [{ rpcUrl: 'rpc_url', chainId: 1, ticker: 'ETH', nickname: '' }] )
preferencesController.removeFromFrequentRpcList('other_rpc_url')
preferencesController.removeFromFrequentRpcList('http://localhost:8545')
preferencesController.removeFromFrequentRpcList('rpc_url')
assert.deepEqual(preferencesController.store.getState().frequentRpcListDetail, [])
})
})
}) })

@ -1133,7 +1133,7 @@ describe('Actions', () => {
{ type: 'DISPLAY_WARNING', value: 'Had a problem changing networks!' }, { type: 'DISPLAY_WARNING', value: 'Had a problem changing networks!' },
] ]
setRpcTargetSpy.callsFake((newRpc, callback) => { setRpcTargetSpy.callsFake((newRpc, chainId, ticker, nickname, callback) => {
callback(new Error('error')) callback(new Error('error'))
}) })

@ -309,7 +309,7 @@ var actions = {
setPreference, setPreference,
updatePreferences, updatePreferences,
UPDATE_PREFERENCES: 'UPDATE_PREFERENCES', UPDATE_PREFERENCES: 'UPDATE_PREFERENCES',
setUseETHAsPrimaryCurrencyPreference, setUseNativeCurrencyAsPrimaryCurrencyPreference,
setMouseUserState, setMouseUserState,
SET_MOUSE_USER_STATE: 'SET_MOUSE_USER_STATE', SET_MOUSE_USER_STATE: 'SET_MOUSE_USER_STATE',
@ -1874,10 +1874,10 @@ function updateProviderType (type) {
} }
} }
function setRpcTarget (newRpc) { function setRpcTarget (newRpc, chainId, ticker = 'ETH', nickname = '') {
return (dispatch) => { return (dispatch) => {
log.debug(`background.setRpcTarget: ${newRpc}`) log.debug(`background.setRpcTarget: ${newRpc} ${chainId} ${ticker} ${nickname}`)
background.setCustomRpc(newRpc, (err, result) => { background.setCustomRpc(newRpc, chainId, ticker, nickname, (err, result) => {
if (err) { if (err) {
log.error(err) log.error(err)
return dispatch(actions.displayWarning('Had a problem changing networks!')) return dispatch(actions.displayWarning('Had a problem changing networks!'))
@ -2330,8 +2330,8 @@ function updatePreferences (value) {
} }
} }
function setUseETHAsPrimaryCurrencyPreference (value) { function setUseNativeCurrencyAsPrimaryCurrencyPreference (value) {
return setPreference('useETHAsPrimaryCurrency', value) return setPreference('useNativeCurrencyAsPrimaryCurrency', value)
} }
function setNetworkNonce (networkNonce) { function setNetworkNonce (networkNonce) {

@ -101,7 +101,7 @@ class App extends Component {
network, network,
isMouseUser, isMouseUser,
provider, provider,
frequentRpcList, frequentRpcListDetail,
currentView, currentView,
setMouseUserState, setMouseUserState,
sidebar, sidebar,
@ -147,7 +147,7 @@ class App extends Component {
// network dropdown // network dropdown
h(NetworkDropdown, { h(NetworkDropdown, {
provider, provider,
frequentRpcList, frequentRpcListDetail,
}, []), }, []),
h(AccountMenu), h(AccountMenu),
@ -230,7 +230,7 @@ App.propTypes = {
alertMessage: PropTypes.string, alertMessage: PropTypes.string,
network: PropTypes.string, network: PropTypes.string,
provider: PropTypes.object, provider: PropTypes.object,
frequentRpcList: PropTypes.array, frequentRpcListDetail: PropTypes.array,
currentView: PropTypes.object, currentView: PropTypes.object,
sidebar: PropTypes.object, sidebar: PropTypes.object,
alertOpen: PropTypes.bool, alertOpen: PropTypes.bool,
@ -322,7 +322,7 @@ function mapStateToProps (state) {
forgottenPassword: state.appState.forgottenPassword, forgottenPassword: state.appState.forgottenPassword,
nextUnreadNotice, nextUnreadNotice,
lostAccounts, lostAccounts,
frequentRpcList: state.metamask.frequentRpcList || [], frequentRpcListDetail: state.metamask.frequentRpcListDetail || [],
currentCurrency: state.metamask.currentCurrency, currentCurrency: state.metamask.currentCurrency,
isMouseUser: state.appState.isMouseUser, isMouseUser: state.appState.isMouseUser,
betaUI: state.metamask.featureFlags.betaUI, betaUI: state.metamask.featureFlags.betaUI,

@ -6,10 +6,11 @@ const genAccountLink = require('etherscan-link').createAccountLink
const connect = require('react-redux').connect const connect = require('react-redux').connect
const Dropdown = require('./dropdown').Dropdown const Dropdown = require('./dropdown').Dropdown
const DropdownMenuItem = require('./dropdown').DropdownMenuItem const DropdownMenuItem = require('./dropdown').DropdownMenuItem
const Identicon = require('./identicon')
const copyToClipboard = require('copy-to-clipboard') const copyToClipboard = require('copy-to-clipboard')
const { checksumAddress } = require('../util') const { checksumAddress } = require('../util')
import Identicon from './identicon'
class AccountDropdowns extends Component { class AccountDropdowns extends Component {
constructor (props) { constructor (props) {
super(props) super(props)

@ -7,10 +7,10 @@ const PropTypes = require('prop-types')
const h = require('react-hyperscript') const h = require('react-hyperscript')
const actions = require('../../actions') const actions = require('../../actions')
const { Menu, Item, Divider, CloseArea } = require('../dropdowns/components/menu') const { Menu, Item, Divider, CloseArea } = require('../dropdowns/components/menu')
const Identicon = require('../identicon')
const { ENVIRONMENT_TYPE_POPUP } = require('../../../../app/scripts/lib/enums') const { ENVIRONMENT_TYPE_POPUP } = require('../../../../app/scripts/lib/enums')
const { getEnvironmentType } = require('../../../../app/scripts/lib/util') const { getEnvironmentType } = require('../../../../app/scripts/lib/util')
const Tooltip = require('../tooltip') const Tooltip = require('../tooltip')
import Identicon from '../identicon'
import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display' import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'
import { PRIMARY } from '../../constants/common' import { PRIMARY } from '../../constants/common'

@ -1,7 +1,7 @@
const inherits = require('util').inherits const inherits = require('util').inherits
const Component = require('react').Component const Component = require('react').Component
const h = require('react-hyperscript') const h = require('react-hyperscript')
const Identicon = require('./identicon') import Identicon from './identicon'
const formatBalance = require('../util').formatBalance const formatBalance = require('../util').formatBalance
const addressSummary = require('../util').addressSummary const addressSummary = require('../util').addressSummary

@ -2,13 +2,13 @@ import React, { PureComponent } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import classnames from 'classnames' import classnames from 'classnames'
import { matchPath } from 'react-router-dom' import { matchPath } from 'react-router-dom'
import Identicon from '../identicon'
const { const {
ENVIRONMENT_TYPE_NOTIFICATION, ENVIRONMENT_TYPE_NOTIFICATION,
ENVIRONMENT_TYPE_POPUP, ENVIRONMENT_TYPE_POPUP,
} = require('../../../../app/scripts/lib/enums') } = require('../../../../app/scripts/lib/enums')
const { DEFAULT_ROUTE, INITIALIZE_ROUTE, CONFIRM_TRANSACTION_ROUTE } = require('../../routes') const { DEFAULT_ROUTE, INITIALIZE_ROUTE, CONFIRM_TRANSACTION_ROUTE } = require('../../routes')
const Identicon = require('../identicon')
const NetworkIndicator = require('../network') const NetworkIndicator = require('../network')
export default class AppHeader extends PureComponent { export default class AppHeader extends PureComponent {

@ -2,11 +2,11 @@ const Component = require('react').Component
const connect = require('react-redux').connect const connect = require('react-redux').connect
const h = require('react-hyperscript') const h = require('react-hyperscript')
const inherits = require('util').inherits const inherits = require('util').inherits
const TokenBalance = require('./token-balance') import TokenBalance from './token-balance'
const Identicon = require('./identicon') import Identicon from './identicon'
import UserPreferencedCurrencyDisplay from './user-preferenced-currency-display' import UserPreferencedCurrencyDisplay from './user-preferenced-currency-display'
import { PRIMARY, SECONDARY } from '../constants/common' import { PRIMARY, SECONDARY } from '../constants/common'
const { getAssetImages, conversionRateSelector, getCurrentCurrency} = require('../selectors') const { getNativeCurrency, getAssetImages, conversionRateSelector, getCurrentCurrency} = require('../selectors')
const { formatBalance } = require('../util') const { formatBalance } = require('../util')
@ -21,6 +21,7 @@ function mapStateToProps (state) {
return { return {
account, account,
network, network,
nativeCurrency: getNativeCurrency(state),
conversionRate: conversionRateSelector(state), conversionRate: conversionRateSelector(state),
currentCurrency: getCurrentCurrency(state), currentCurrency: getCurrentCurrency(state),
assetImages: getAssetImages(state), assetImages: getAssetImages(state),
@ -66,10 +67,10 @@ BalanceComponent.prototype.renderTokenBalance = function () {
BalanceComponent.prototype.renderBalance = function () { BalanceComponent.prototype.renderBalance = function () {
const props = this.props const props = this.props
const { account } = props const { account, nativeCurrency } = props
const balanceValue = account && account.balance const balanceValue = account && account.balance
const needsParse = 'needsParse' in props ? props.needsParse : true const needsParse = 'needsParse' in props ? props.needsParse : true
const formattedBalance = balanceValue ? formatBalance(balanceValue, 6, needsParse) : '...' const formattedBalance = balanceValue ? formatBalance(balanceValue, 6, needsParse, nativeCurrency) : '...'
const showFiat = 'showFiat' in props ? props.showFiat : true const showFiat = 'showFiat' in props ? props.showFiat : true
if (formattedBalance === 'None' || formattedBalance === '...') { if (formattedBalance === 'None' || formattedBalance === '...') {
@ -81,11 +82,12 @@ BalanceComponent.prototype.renderBalance = function () {
} }
return h('div.flex-column.balance-display', {}, [ return h('div.flex-column.balance-display', {}, [
h('div.token-amount', {}, h(UserPreferencedCurrencyDisplay, { h(UserPreferencedCurrencyDisplay, {
className: 'token-amount',
value: balanceValue, value: balanceValue,
type: PRIMARY, type: PRIMARY,
ethNumberOfDecimals: 3, ethNumberOfDecimals: 3,
})), }),
showFiat && h(UserPreferencedCurrencyDisplay, { showFiat && h(UserPreferencedCurrencyDisplay, {
value: balanceValue, value: balanceValue,

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

@ -26,14 +26,15 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
const convertedValue = getValueFromWeiHex({ const convertedValue = getValueFromWeiHex({
value, toCurrency, conversionRate, numberOfDecimals, toDenomination: denomination, value, toCurrency, conversionRate, numberOfDecimals, toDenomination: denomination,
}) })
const formattedValue = formatCurrency(convertedValue, toCurrency) const displayValue = formatCurrency(convertedValue, toCurrency)
const displayValue = hideLabel ? formattedValue : `${formattedValue} ${toCurrency.toUpperCase()}` const suffix = hideLabel ? undefined : toCurrency.toUpperCase()
return { return {
...restStateProps, ...restStateProps,
...dispatchProps, ...dispatchProps,
...restOwnProps, ...restOwnProps,
displayValue, displayValue,
suffix,
} }
} }

@ -7,4 +7,8 @@
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
&__suffix {
padding-left: 4px;
}
} }

@ -45,7 +45,8 @@ describe('CurrencyDisplay container', () => {
currency: 'usd', currency: 'usd',
}, },
result: { result: {
displayValue: '$2.80 USD', displayValue: '$2.80',
suffix: 'USD',
}, },
}, },
{ {
@ -53,7 +54,8 @@ describe('CurrencyDisplay container', () => {
value: '0x2386f26fc10000', value: '0x2386f26fc10000',
}, },
result: { result: {
displayValue: '$2.80 USD', displayValue: '$2.80',
suffix: 'USD',
}, },
}, },
{ {
@ -63,7 +65,8 @@ describe('CurrencyDisplay container', () => {
numberOfDecimals: 3, numberOfDecimals: 3,
}, },
result: { result: {
displayValue: '1.266 ETH', displayValue: '1.266',
suffix: 'ETH',
}, },
}, },
{ {
@ -75,6 +78,7 @@ describe('CurrencyDisplay container', () => {
}, },
result: { result: {
displayValue: '1.266', displayValue: '1.266',
suffix: undefined,
}, },
}, },
{ {
@ -86,6 +90,7 @@ describe('CurrencyDisplay container', () => {
}, },
result: { result: {
displayValue: '1', displayValue: '1',
suffix: undefined,
}, },
}, },
{ {
@ -97,6 +102,7 @@ describe('CurrencyDisplay container', () => {
}, },
result: { result: {
displayValue: '1000000000', displayValue: '1000000000',
suffix: undefined,
}, },
}, },
{ {
@ -108,6 +114,7 @@ describe('CurrencyDisplay container', () => {
}, },
result: { result: {
displayValue: '1e-9', displayValue: '1e-9',
suffix: undefined,
}, },
}, },
] ]

@ -14,6 +14,7 @@ export default class CurrencyInput extends PureComponent {
static propTypes = { static propTypes = {
conversionRate: PropTypes.number, conversionRate: PropTypes.number,
currentCurrency: PropTypes.string, currentCurrency: PropTypes.string,
nativeCurrency: PropTypes.string,
onChange: PropTypes.func, onChange: PropTypes.func,
onBlur: PropTypes.func, onBlur: PropTypes.func,
suffix: PropTypes.string, suffix: PropTypes.string,
@ -77,13 +78,13 @@ export default class CurrencyInput extends PureComponent {
} }
renderConversionComponent () { renderConversionComponent () {
const { useFiat, currentCurrency } = this.props const { useFiat, currentCurrency, nativeCurrency } = this.props
const { hexValue } = this.state const { hexValue } = this.state
let currency, numberOfDecimals let currency, numberOfDecimals
if (useFiat) { if (useFiat) {
// Display ETH // Display ETH
currency = ETH currency = nativeCurrency || ETH
numberOfDecimals = 6 numberOfDecimals = 6
} else { } else {
// Display Fiat // Display Fiat

@ -3,18 +3,19 @@ import CurrencyInput from './currency-input.component'
import { ETH } from '../../constants/common' import { ETH } from '../../constants/common'
const mapStateToProps = state => { const mapStateToProps = state => {
const { metamask: { currentCurrency, conversionRate } } = state const { metamask: { nativeCurrency, currentCurrency, conversionRate } } = state
return { return {
nativeCurrency,
currentCurrency, currentCurrency,
conversionRate, conversionRate,
} }
} }
const mergeProps = (stateProps, dispatchProps, ownProps) => { const mergeProps = (stateProps, dispatchProps, ownProps) => {
const { currentCurrency } = stateProps const { nativeCurrency, currentCurrency } = stateProps
const { useFiat } = ownProps const { useFiat } = ownProps
const suffix = useFiat ? currentCurrency.toUpperCase() : ETH const suffix = useFiat ? currentCurrency.toUpperCase() : nativeCurrency || ETH
return { return {
...stateProps, ...stateProps,

@ -22,6 +22,7 @@ describe('CurrencyInput Component', () => {
it('should render properly with a suffix', () => { it('should render properly with a suffix', () => {
const mockStore = { const mockStore = {
metamask: { metamask: {
nativeCurrency: 'ETH',
currentCurrency: 'usd', currentCurrency: 'usd',
conversionRate: 231.06, conversionRate: 231.06,
}, },
@ -32,6 +33,7 @@ describe('CurrencyInput Component', () => {
<Provider store={store}> <Provider store={store}>
<CurrencyInput <CurrencyInput
suffix="ETH" suffix="ETH"
nativeCurrency="ETH"
/> />
</Provider> </Provider>
) )
@ -45,6 +47,7 @@ describe('CurrencyInput Component', () => {
it('should render properly with an ETH value', () => { it('should render properly with an ETH value', () => {
const mockStore = { const mockStore = {
metamask: { metamask: {
nativeCurrency: 'ETH',
currentCurrency: 'usd', currentCurrency: 'usd',
conversionRate: 231.06, conversionRate: 231.06,
}, },
@ -56,6 +59,7 @@ describe('CurrencyInput Component', () => {
<CurrencyInput <CurrencyInput
value="de0b6b3a7640000" value="de0b6b3a7640000"
suffix="ETH" suffix="ETH"
nativeCurrency="ETH"
currentCurrency="usd" currentCurrency="usd"
conversionRate={231.06} conversionRate={231.06}
/> />
@ -75,6 +79,7 @@ describe('CurrencyInput Component', () => {
it('should render properly with a fiat value', () => { it('should render properly with a fiat value', () => {
const mockStore = { const mockStore = {
metamask: { metamask: {
nativeCurrency: 'ETH',
currentCurrency: 'usd', currentCurrency: 'usd',
conversionRate: 231.06, conversionRate: 231.06,
}, },
@ -87,6 +92,7 @@ describe('CurrencyInput Component', () => {
value="f602f2234d0ea" value="f602f2234d0ea"
suffix="USD" suffix="USD"
useFiat useFiat
nativeCurrency="ETH"
currentCurrency="usd" currentCurrency="usd"
conversionRate={231.06} conversionRate={231.06}
/> />
@ -116,6 +122,7 @@ describe('CurrencyInput Component', () => {
it('should call onChange and onBlur on input changes with the hex value for ETH', () => { it('should call onChange and onBlur on input changes with the hex value for ETH', () => {
const mockStore = { const mockStore = {
metamask: { metamask: {
nativeCurrency: 'ETH',
currentCurrency: 'usd', currentCurrency: 'usd',
conversionRate: 231.06, conversionRate: 231.06,
}, },
@ -127,6 +134,7 @@ describe('CurrencyInput Component', () => {
onChange={handleChangeSpy} onChange={handleChangeSpy}
onBlur={handleBlurSpy} onBlur={handleBlurSpy}
suffix="ETH" suffix="ETH"
nativeCurrency="ETH"
currentCurrency="usd" currentCurrency="usd"
conversionRate={231.06} conversionRate={231.06}
/> />
@ -160,6 +168,7 @@ describe('CurrencyInput Component', () => {
it('should call onChange and onBlur on input changes with the hex value for fiat', () => { it('should call onChange and onBlur on input changes with the hex value for fiat', () => {
const mockStore = { const mockStore = {
metamask: { metamask: {
nativeCurrency: 'ETH',
currentCurrency: 'usd', currentCurrency: 'usd',
conversionRate: 231.06, conversionRate: 231.06,
}, },
@ -171,6 +180,7 @@ describe('CurrencyInput Component', () => {
onChange={handleChangeSpy} onChange={handleChangeSpy}
onBlur={handleBlurSpy} onBlur={handleBlurSpy}
suffix="USD" suffix="USD"
nativeCurrency="ETH"
currentCurrency="usd" currentCurrency="usd"
conversionRate={231.06} conversionRate={231.06}
useFiat useFiat
@ -205,6 +215,7 @@ describe('CurrencyInput Component', () => {
it('should change the state and pass in a new decimalValue when props.value changes', () => { it('should change the state and pass in a new decimalValue when props.value changes', () => {
const mockStore = { const mockStore = {
metamask: { metamask: {
nativeCurrency: 'ETH',
currentCurrency: 'usd', currentCurrency: 'usd',
conversionRate: 231.06, conversionRate: 231.06,
}, },
@ -216,6 +227,7 @@ describe('CurrencyInput Component', () => {
onChange={handleChangeSpy} onChange={handleChangeSpy}
onBlur={handleBlurSpy} onBlur={handleBlurSpy}
suffix="USD" suffix="USD"
nativeCurrency="ETH"
currentCurrency="usd" currentCurrency="usd"
conversionRate={231.06} conversionRate={231.06}
useFiat useFiat

@ -20,12 +20,14 @@ describe('CurrencyInput container', () => {
metamask: { metamask: {
conversionRate: 280.45, conversionRate: 280.45,
currentCurrency: 'usd', currentCurrency: 'usd',
nativeCurrency: 'ETH',
}, },
} }
assert.deepEqual(mapStateToProps(mockState), { assert.deepEqual(mapStateToProps(mockState), {
conversionRate: 280.45, conversionRate: 280.45,
currentCurrency: 'usd', currentCurrency: 'usd',
nativeCurrency: 'ETH',
}) })
}) })
}) })
@ -35,12 +37,14 @@ describe('CurrencyInput container', () => {
const mockStateProps = { const mockStateProps = {
conversionRate: 280.45, conversionRate: 280.45,
currentCurrency: 'usd', currentCurrency: 'usd',
nativeCurrency: 'ETH',
} }
const mockDispatchProps = {} const mockDispatchProps = {}
assert.deepEqual(mergeProps(mockStateProps, mockDispatchProps, { useFiat: true }), { assert.deepEqual(mergeProps(mockStateProps, mockDispatchProps, { useFiat: true }), {
conversionRate: 280.45, conversionRate: 280.45,
currentCurrency: 'usd', currentCurrency: 'usd',
nativeCurrency: 'ETH',
useFiat: true, useFiat: true,
suffix: 'USD', suffix: 'USD',
}) })
@ -48,6 +52,7 @@ describe('CurrencyInput container', () => {
assert.deepEqual(mergeProps(mockStateProps, mockDispatchProps, {}), { assert.deepEqual(mergeProps(mockStateProps, mockDispatchProps, {}), {
conversionRate: 280.45, conversionRate: 280.45,
currentCurrency: 'usd', currentCurrency: 'usd',
nativeCurrency: 'ETH',
suffix: 'ETH', suffix: 'ETH',
}) })
}) })

@ -6,7 +6,7 @@ const genAccountLink = require('../../../../lib/account-link.js')
const connect = require('react-redux').connect const connect = require('react-redux').connect
const Dropdown = require('./dropdown').Dropdown const Dropdown = require('./dropdown').Dropdown
const DropdownMenuItem = require('./dropdown').DropdownMenuItem const DropdownMenuItem = require('./dropdown').DropdownMenuItem
const Identicon = require('../../identicon') import Identicon from '../../identicon'
const { checksumAddress } = require('../../../util') const { checksumAddress } = require('../../../util')
const copyToClipboard = require('copy-to-clipboard') const copyToClipboard = require('copy-to-clipboard')
const { formatBalance } = require('../../../util') const { formatBalance } = require('../../../util')
@ -26,14 +26,14 @@ class AccountDropdowns extends Component {
} }
renderAccounts () { renderAccounts () {
const { identities, accounts, selected, menuItemStyles, actions, keyrings } = this.props const { identities, accounts, selected, menuItemStyles, actions, keyrings, ticker } = this.props
return Object.keys(identities).map((key, index) => { return Object.keys(identities).map((key, index) => {
const identity = identities[key] const identity = identities[key]
const isSelected = identity.address === selected const isSelected = identity.address === selected
const balanceValue = accounts[key].balance const balanceValue = accounts[key].balance
const formattedBalance = balanceValue ? formatBalance(balanceValue, 6) : '...' const formattedBalance = balanceValue ? formatBalance(balanceValue, 6, true, ticker) : '...'
const simpleAddress = identity.address.substring(2).toLowerCase() const simpleAddress = identity.address.substring(2).toLowerCase()
const keyring = keyrings.find((kr) => { const keyring = keyrings.find((kr) => {
@ -421,6 +421,7 @@ AccountDropdowns.propTypes = {
network: PropTypes.number, network: PropTypes.number,
// actions.showExportPrivateKeyModal: , // actions.showExportPrivateKeyModal: ,
style: PropTypes.object, style: PropTypes.object,
ticker: PropTypes.string,
enableAccountsSelector: PropTypes.bool, enableAccountsSelector: PropTypes.bool,
enableAccountOption: PropTypes.bool, enableAccountOption: PropTypes.bool,
enableAccountOptions: PropTypes.bool, enableAccountOptions: PropTypes.bool,
@ -458,6 +459,7 @@ const mapDispatchToProps = (dispatch) => {
function mapStateToProps (state) { function mapStateToProps (state) {
return { return {
ticker: state.metamask.ticker,
keyrings: state.metamask.keyrings, keyrings: state.metamask.keyrings,
sidebarOpen: state.appState.sidebar.isOpen, sidebarOpen: state.appState.sidebar.isOpen,
} }

@ -24,8 +24,9 @@ const notToggleElementClassnames = [
function mapStateToProps (state) { function mapStateToProps (state) {
return { return {
provider: state.metamask.provider, provider: state.metamask.provider,
frequentRpcList: state.metamask.frequentRpcList || [], frequentRpcListDetail: state.metamask.frequentRpcListDetail || [],
networkDropdownOpen: state.appState.networkDropdownOpen, networkDropdownOpen: state.appState.networkDropdownOpen,
network: state.metamask.network,
} }
} }
@ -40,8 +41,8 @@ function mapDispatchToProps (dispatch) {
setDefaultRpcTarget: type => { setDefaultRpcTarget: type => {
dispatch(actions.setDefaultRpcTarget(type)) dispatch(actions.setDefaultRpcTarget(type))
}, },
setRpcTarget: (target) => { setRpcTarget: (target, network, ticker, nickname) => {
dispatch(actions.setRpcTarget(target)) dispatch(actions.setRpcTarget(target, network, ticker, nickname))
}, },
delRpcTarget: (target) => { delRpcTarget: (target) => {
dispatch(actions.delRpcTarget(target)) dispatch(actions.delRpcTarget(target))
@ -71,7 +72,7 @@ module.exports = compose(
NetworkDropdown.prototype.render = function () { NetworkDropdown.prototype.render = function () {
const props = this.props const props = this.props
const { provider: { type: providerType, rpcTarget: activeNetwork } } = props const { provider: { type: providerType, rpcTarget: activeNetwork } } = props
const rpcList = props.frequentRpcList const rpcListDetail = props.frequentRpcListDetail
const isOpen = this.props.networkDropdownOpen const isOpen = this.props.networkDropdownOpen
const dropdownMenuItemStyle = { const dropdownMenuItemStyle = {
fontSize: '16px', fontSize: '16px',
@ -225,7 +226,7 @@ NetworkDropdown.prototype.render = function () {
), ),
this.renderCustomOption(props.provider), this.renderCustomOption(props.provider),
this.renderCommonRpc(rpcList, props.provider), this.renderCommonRpc(rpcListDetail, props.provider),
h( h(
DropdownMenuItem, DropdownMenuItem,
@ -267,28 +268,33 @@ NetworkDropdown.prototype.getNetworkName = function () {
} else if (providerName === 'rinkeby') { } else if (providerName === 'rinkeby') {
name = this.context.t('rinkeby') name = this.context.t('rinkeby')
} else { } else {
name = this.context.t('unknownNetwork') name = provider.nickname || this.context.t('unknownNetwork')
} }
return name return name
} }
NetworkDropdown.prototype.renderCommonRpc = function (rpcList, provider) { NetworkDropdown.prototype.renderCommonRpc = function (rpcListDetail, provider) {
const props = this.props const props = this.props
const reversedRpcList = rpcList.slice().reverse() const reversedRpcListDetail = rpcListDetail.slice().reverse()
const network = props.network
return reversedRpcList.map((rpc) => { return reversedRpcListDetail.map((entry) => {
const rpc = entry.rpcUrl
const ticker = entry.ticker || 'ETH'
const nickname = entry.nickname || ''
const currentRpcTarget = provider.type === 'rpc' && rpc === provider.rpcTarget const currentRpcTarget = provider.type === 'rpc' && rpc === provider.rpcTarget
if ((rpc === 'http://localhost:8545') || currentRpcTarget) { if ((rpc === 'http://localhost:8545') || currentRpcTarget) {
return null return null
} else { } else {
const chainId = entry.chainId || network
return h( return h(
DropdownMenuItem, DropdownMenuItem,
{ {
key: `common${rpc}`, key: `common${rpc}`,
closeMenu: () => this.props.hideNetworkDropdown(), closeMenu: () => this.props.hideNetworkDropdown(),
onClick: () => props.setRpcTarget(rpc), onClick: () => props.setRpcTarget(rpc, chainId, ticker, nickname),
style: { style: {
fontSize: '16px', fontSize: '16px',
lineHeight: '20px', lineHeight: '20px',
@ -302,7 +308,7 @@ NetworkDropdown.prototype.renderCommonRpc = function (rpcList, provider) {
style: { style: {
color: currentRpcTarget ? '#ffffff' : '#9b9b9b', color: currentRpcTarget ? '#ffffff' : '#9b9b9b',
}, },
}, rpc), }, nickname || rpc),
h('i.fa.fa-times.delete', h('i.fa.fa-times.delete',
{ {
onClick: (e) => { onClick: (e) => {
@ -317,8 +323,9 @@ NetworkDropdown.prototype.renderCommonRpc = function (rpcList, provider) {
} }
NetworkDropdown.prototype.renderCustomOption = function (provider) { NetworkDropdown.prototype.renderCustomOption = function (provider) {
const { rpcTarget, type } = provider const { rpcTarget, type, ticker, nickname } = provider
const props = this.props const props = this.props
const network = props.network
if (type !== 'rpc') return null if (type !== 'rpc') return null
@ -332,7 +339,7 @@ NetworkDropdown.prototype.renderCustomOption = function (provider) {
DropdownMenuItem, DropdownMenuItem,
{ {
key: rpcTarget, key: rpcTarget,
onClick: () => props.setRpcTarget(rpcTarget), onClick: () => props.setRpcTarget(rpcTarget, network, ticker, nickname),
closeMenu: () => this.props.hideNetworkDropdown(), closeMenu: () => this.props.hideNetworkDropdown(),
style: { style: {
fontSize: '16px', fontSize: '16px',
@ -347,7 +354,7 @@ NetworkDropdown.prototype.renderCustomOption = function (provider) {
style: { style: {
color: '#ffffff', color: '#ffffff',
}, },
}, rpcTarget), }, nickname || rpcTarget),
] ]
) )
} }

@ -45,8 +45,8 @@ describe('Network Dropdown', () => {
provider: { provider: {
'type': 'test', 'type': 'test',
}, },
frequentRpcList: [ frequentRpcListDetail: [
'http://localhost:7545', { rpcUrl: 'http://localhost:7545' },
], ],
}, },
appState: { appState: {

@ -1,5 +1,6 @@
const { Component } = require('react') const { Component } = require('react')
const h = require('react-hyperscript') const h = require('react-hyperscript')
const connect = require('react-redux').connect
const { inherits } = require('util') const { inherits } = require('util')
const { const {
formatBalance, formatBalance,
@ -8,7 +9,12 @@ const {
const Tooltip = require('./tooltip.js') const Tooltip = require('./tooltip.js')
const FiatValue = require('./fiat-value.js') const FiatValue = require('./fiat-value.js')
module.exports = EthBalanceComponent module.exports = connect(mapStateToProps)(EthBalanceComponent)
function mapStateToProps (state) {
return {
ticker: state.metamask.ticker,
}
}
inherits(EthBalanceComponent, Component) inherits(EthBalanceComponent, Component)
function EthBalanceComponent () { function EthBalanceComponent () {
@ -17,9 +23,9 @@ function EthBalanceComponent () {
EthBalanceComponent.prototype.render = function () { EthBalanceComponent.prototype.render = function () {
const props = this.props const props = this.props
const { value, style, width, needsParse = true } = props const { ticker, value, style, width, needsParse = true } = props
const formattedValue = value ? formatBalance(value, 6, needsParse) : '...' const formattedValue = value ? formatBalance(value, 6, needsParse, ticker) : '...'
return ( return (

@ -1,124 +0,0 @@
const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
const isNode = require('detect-node')
const findDOMNode = require('react-dom').findDOMNode
const jazzicon = require('jazzicon')
const iconFactoryGen = require('../../lib/icon-factory')
const iconFactory = iconFactoryGen(jazzicon)
const { toDataUrl } = require('../../lib/blockies')
module.exports = connect(mapStateToProps)(IdenticonComponent)
inherits(IdenticonComponent, Component)
function IdenticonComponent () {
Component.call(this)
this.defaultDiameter = 46
}
function mapStateToProps (state) {
return {
useBlockie: state.metamask.useBlockie,
}
}
IdenticonComponent.prototype.render = function () {
var props = this.props
const { className = '', address, image } = props
var diameter = props.diameter || this.defaultDiameter
const style = {
height: diameter,
width: diameter,
borderRadius: diameter / 2,
}
if (image) {
return h('img', {
className: `${className} identicon`,
src: image,
style: {
...style,
},
})
} else if (address) {
return h('div', {
className: `${className} identicon`,
key: 'identicon-' + address,
style: {
display: 'flex',
flexShrink: 0,
alignItems: 'center',
justifyContent: 'center',
...style,
overflow: 'hidden',
},
})
} else {
return h('img.balance-icon', {
className,
src: './images/eth_logo.svg',
style: {
...style,
},
})
}
}
IdenticonComponent.prototype.componentDidMount = function () {
var props = this.props
const { address, useBlockie } = props
if (!address) return
if (!isNode) {
// eslint-disable-next-line react/no-find-dom-node
var container = findDOMNode(this)
const diameter = props.diameter || this.defaultDiameter
if (useBlockie) {
_generateBlockie(container, address, diameter)
} else {
_generateJazzicon(container, address, diameter)
}
}
}
IdenticonComponent.prototype.componentDidUpdate = function () {
var props = this.props
const { address, useBlockie } = props
if (!address) return
if (!isNode) {
// eslint-disable-next-line react/no-find-dom-node
var container = findDOMNode(this)
var children = container.children
for (var i = 0; i < children.length; i++) {
container.removeChild(children[i])
}
const diameter = props.diameter || this.defaultDiameter
if (useBlockie) {
_generateBlockie(container, address, diameter)
} else {
_generateJazzicon(container, address, diameter)
}
}
}
function _generateBlockie (container, address, diameter) {
const img = new Image()
img.src = toDataUrl(address)
img.height = diameter
img.width = diameter
container.appendChild(img)
}
function _generateJazzicon (container, address, diameter) {
const img = iconFactory.iconForAddress(address, diameter)
container.appendChild(img)
}

@ -0,0 +1,99 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import { toDataUrl } from '../../../lib/blockies'
import contractMap from 'eth-contract-metadata'
import { checksumAddress } from '../../../app/util'
import Jazzicon from '../jazzicon'
const getStyles = diameter => (
{
height: diameter,
width: diameter,
borderRadius: diameter / 2,
}
)
export default class Identicon extends PureComponent {
static propTypes = {
address: PropTypes.string,
className: PropTypes.string,
diameter: PropTypes.number,
image: PropTypes.string,
useBlockie: PropTypes.bool,
}
static defaultProps = {
diameter: 46,
}
renderImage () {
const { className, diameter, image } = this.props
return (
<img
className={classnames('identicon', className)}
src={image}
style={getStyles(diameter)}
/>
)
}
renderJazzicon () {
const { address, className, diameter } = this.props
return (
<Jazzicon
address={address}
diameter={diameter}
className={classnames('identicon', className)}
style={getStyles(diameter)}
/>
)
}
renderBlockie () {
const { address, className, diameter } = this.props
return (
<div
className={classnames('identicon', className)}
style={getStyles(diameter)}
>
<img
src={toDataUrl(address)}
height={diameter}
width={diameter}
/>
</div>
)
}
render () {
const { className, address, image, diameter, useBlockie } = this.props
if (image) {
return this.renderImage()
}
if (address) {
const checksummedAddress = checksumAddress(address)
if (contractMap[checksummedAddress] && contractMap[checksummedAddress].logo) {
return this.renderJazzicon()
}
return useBlockie
? this.renderBlockie()
: this.renderJazzicon()
}
return (
<img
className={classnames('balance-icon', className)}
src="./images/eth_logo.svg"
style={getStyles(diameter)}
/>
)
}
}

@ -0,0 +1,12 @@
import { connect } from 'react-redux'
import Identicon from './identicon.component'
const mapStateToProps = state => {
const { metamask: { useBlockie } } = state
return {
useBlockie,
}
}
export default connect(mapStateToProps)(Identicon)

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

@ -0,0 +1,7 @@
.identicon {
display: flex;
flex-shrink: 0;
align-items: center;
justify-content: center;
overflow: hidden;
}

@ -3,11 +3,9 @@ import assert from 'assert'
import thunk from 'redux-thunk' import thunk from 'redux-thunk'
import configureMockStore from 'redux-mock-store' import configureMockStore from 'redux-mock-store'
import { mount } from 'enzyme' import { mount } from 'enzyme'
import Identicon from '../identicon.component'
import IdenticonComponent from '../../../../../ui/app/components/identicon' describe('Identicon', () => {
describe('Identicon Component', () => {
const state = { const state = {
metamask: { metamask: {
useBlockie: false, useBlockie: false,
@ -19,18 +17,35 @@ describe('Identicon Component', () => {
const store = mockStore(state) const store = mockStore(state)
it('renders default eth_logo identicon with no props', () => { it('renders default eth_logo identicon with no props', () => {
const wrapper = mount(<IdenticonComponent store={store}/>) const wrapper = mount(
<Identicon store={store}/>
)
assert.equal(wrapper.find('img.balance-icon').prop('src'), './images/eth_logo.svg') assert.equal(wrapper.find('img.balance-icon').prop('src'), './images/eth_logo.svg')
}) })
it('renders custom image and add className props', () => { it('renders custom image and add className props', () => {
const wrapper = mount(<IdenticonComponent store={store} className={'test-image'} image={'test-image'} />) const wrapper = mount(
assert.equal(wrapper.find('img.test-image').prop('className'), 'test-image identicon') <Identicon
store={store}
className="test-image"
image="test-image"
/>
)
assert.equal(wrapper.find('img.test-image').prop('className'), 'identicon test-image')
assert.equal(wrapper.find('img.test-image').prop('src'), 'test-image') assert.equal(wrapper.find('img.test-image').prop('src'), 'test-image')
}) })
it('renders div with address prop', () => { it('renders div with address prop', () => {
const wrapper = mount(<IdenticonComponent store={store} className={'test-address'} address={'0xTest'} />) const wrapper = mount(
assert.equal(wrapper.find('div.test-address').prop('className'), 'test-address identicon') <Identicon
store={store}
className="test-address"
address="0xTest"
/>
)
assert.equal(wrapper.find('div.test-address').prop('className'), 'identicon test-address')
}) })
}) })

@ -16,6 +16,8 @@
@import './export-text-container/index'; @import './export-text-container/index';
@import './identicon/index';
@import './info-box/index'; @import './info-box/index';
@import './menu-bar/index'; @import './menu-bar/index';

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

@ -0,0 +1,69 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import isNode from 'detect-node'
import { findDOMNode } from 'react-dom'
import jazzicon from 'jazzicon'
import iconFactoryGenerator from '../../../lib/icon-factory'
const iconFactory = iconFactoryGenerator(jazzicon)
/**
* Wrapper around the jazzicon library to return a React component, as the library returns an
* HTMLDivElement which needs to be appended.
*/
export default class Jazzicon extends PureComponent {
static propTypes = {
address: PropTypes.string.isRequired,
className: PropTypes.string,
diameter: PropTypes.number,
style: PropTypes.object,
}
static defaultProps = {
diameter: 46,
}
componentDidMount () {
if (!isNode) {
this.appendJazzicon()
}
}
componentDidUpdate (prevProps) {
const { address: prevAddress } = prevProps
const { address } = this.props
if (!isNode && address !== prevAddress) {
this.removeExistingChildren()
this.appendJazzicon()
}
}
removeExistingChildren () {
// eslint-disable-next-line react/no-find-dom-node
const container = findDOMNode(this)
const { children } = container
for (let i = 0; i < children.length; i++) {
container.removeChild(children[i])
}
}
appendJazzicon () {
// eslint-disable-next-line react/no-find-dom-node
const container = findDOMNode(this)
const { address, diameter } = this.props
const image = iconFactory.iconForAddress(address, diameter)
container.appendChild(image)
}
render () {
const { className, style } = this.props
return (
<div
className={className}
style={style}
/>
)
}
}

@ -5,7 +5,7 @@ const inherits = require('util').inherits
const connect = require('react-redux').connect const connect = require('react-redux').connect
const actions = require('../../actions') const actions = require('../../actions')
const { getSelectedIdentity } = require('../../selectors') const { getSelectedIdentity } = require('../../selectors')
const Identicon = require('../identicon') import Identicon from '../identicon'
function mapStateToProps (state, ownProps) { function mapStateToProps (state, ownProps) {
return { return {

@ -4,7 +4,7 @@ const h = require('react-hyperscript')
const inherits = require('util').inherits const inherits = require('util').inherits
const connect = require('react-redux').connect const connect = require('react-redux').connect
const actions = require('../../actions') const actions = require('../../actions')
const Identicon = require('../identicon') import Identicon from '../identicon'
function mapStateToProps (state) { function mapStateToProps (state) {
return { return {

@ -27,7 +27,6 @@ import TransactionConfirmed from './transaction-confirmed'
import ConfirmCustomizeGasModal from './customize-gas' import ConfirmCustomizeGasModal from './customize-gas'
import CancelTransaction from './cancel-transaction' import CancelTransaction from './cancel-transaction'
import WelcomeBeta from './welcome-beta' import WelcomeBeta from './welcome-beta'
import TransactionDetails from './transaction-details'
import RejectTransactions from './reject-transactions' import RejectTransactions from './reject-transactions'
const modalContainerBaseStyle = { const modalContainerBaseStyle = {
@ -366,19 +365,6 @@ const MODALS = {
}, },
}, },
TRANSACTION_DETAILS: {
contents: h(TransactionDetails),
mobileModalStyle: {
...modalContainerMobileStyle,
},
laptopModalStyle: {
...modalContainerLaptopStyle,
},
contentStyle: {
borderRadius: '8px',
},
},
REJECT_TRANSACTIONS: { REJECT_TRANSACTIONS: {
contents: h(RejectTransactions), contents: h(RejectTransactions),
mobileModalStyle: { mobileModalStyle: {

@ -1 +0,0 @@
export { default } from './transaction-details.container'

@ -1,54 +0,0 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import Modal from '../../modal'
import TransactionListItemDetails from '../../transaction-list-item-details'
import { hexToDecimal } from '../../../helpers/conversions.util'
export default class TransactionConfirmed extends PureComponent {
static contextTypes = {
t: PropTypes.func,
}
static propTypes = {
hideModal: PropTypes.func,
transaction: PropTypes.object,
onRetry: PropTypes.func,
showRetry: PropTypes.bool,
onCancel: PropTypes.func,
showCancel: PropTypes.bool,
}
handleSubmit = () => {
this.props.hideModal()
}
handleRetry = () => {
const { onRetry, hideModal } = this.props
Promise.resolve(onRetry()).then(() => hideModal())
}
render () {
const { t } = this.context
const { transaction, showRetry, onCancel, showCancel } = this.props
const { txParams: { nonce } = {} } = transaction
const decimalNonce = nonce && hexToDecimal(nonce)
return (
<Modal
onSubmit={this.handleSubmit}
onClose={this.handleSubmit}
submitText={t('ok')}
headerText={t('transactionWithNonce', [`#${decimalNonce}`])}
>
<TransactionListItemDetails
transaction={transaction}
onRetry={this.handleRetry}
showRetry={showRetry}
onCancel={() => onCancel()}
showCancel={showCancel}
/>
</Modal>
)
}
}

@ -1,4 +0,0 @@
import TransactionDetails from './transaction-details.component'
import withModalProps from '../../../higher-order-components/with-modal-props'
export default withModalProps(TransactionDetails)

@ -41,7 +41,7 @@ export default class NetworkDisplay extends Component {
} }
render () { render () {
const { network, provider: { type } } = this.props const { network, provider: { type, nickname } } = this.props
const networkClass = networkToClassHash[network] const networkClass = networkToClassHash[network]
return ( return (
@ -61,7 +61,7 @@ export default class NetworkDisplay extends Component {
/> />
} }
<div className="network-display__name"> <div className="network-display__name">
{ this.context.t(type) } { type === 'rpc' && nickname ? nickname : this.context.t(type) }
</div> </div>
</div> </div>
) )

@ -23,9 +23,10 @@ Network.prototype.render = function () {
const props = this.props const props = this.props
const context = this.context const context = this.context
const networkNumber = props.network const networkNumber = props.network
let providerName let providerName, providerNick
try { try {
providerName = props.provider.type providerName = props.provider.type
providerNick = props.provider.nickname || ''
} catch (e) { } catch (e) {
providerName = null providerName = null
} }
@ -131,7 +132,7 @@ Network.prototype.render = function () {
}, },
}), }),
h('.network-name', context.t('privateNetwork')), h('.network-name', providerNick || context.t('privateNetwork')),
h('i.fa.fa-chevron-down.fa-lg.network-caret'), h('i.fa.fa-chevron-down.fa-lg.network-caret'),
]) ])
} }

@ -5,12 +5,9 @@
color: $crimson; color: $crimson;
} }
&__rpc-save-button { &__advanced-link {
align-self: flex-end; color: $curious-blue;
padding: 5px; padding-left: 5px;
text-transform: uppercase;
color: $dusty-gray;
cursor: pointer;
} }
&__rpc-save-button { &__rpc-save-button {
@ -19,6 +16,9 @@
text-transform: uppercase; text-transform: uppercase;
color: $dusty-gray; color: $dusty-gray;
cursor: pointer; cursor: pointer;
width: 25%;
min-width: 80px;
height: 33px;
} }
&__button--red { &__button--red {

@ -55,12 +55,17 @@ export default class SettingsTab extends PureComponent {
sendHexData: PropTypes.bool, sendHexData: PropTypes.bool,
currentCurrency: PropTypes.string, currentCurrency: PropTypes.string,
conversionDate: PropTypes.number, conversionDate: PropTypes.number,
useETHAsPrimaryCurrency: PropTypes.bool, nativeCurrency: PropTypes.string,
setUseETHAsPrimaryCurrencyPreference: PropTypes.func, useNativeCurrencyAsPrimaryCurrency: PropTypes.bool,
setUseNativeCurrencyAsPrimaryCurrencyPreference: PropTypes.func,
} }
state = { state = {
newRpc: '', newRpc: '',
chainId: '',
showOptions: false,
ticker: '',
nickname: '',
} }
renderCurrentConversion () { renderCurrentConversion () {
@ -121,37 +126,98 @@ export default class SettingsTab extends PureComponent {
renderNewRpcUrl () { renderNewRpcUrl () {
const { t } = this.context const { t } = this.context
const { newRpc } = this.state const { newRpc, chainId, ticker, nickname } = this.state
return ( return (
<div className="settings-page__content-row"> <div className="settings-page__content-row">
<div className="settings-page__content-item"> <div className="settings-page__content-item">
<span>{ t('newRPC') }</span> <span>{ t('newNetwork') }</span>
</div> </div>
<div className="settings-page__content-item"> <div className="settings-page__content-item">
<div className="settings-page__content-item-col"> <div className="settings-page__content-item-col">
<TextField <TextField
type="text" type="text"
id="new-rpc" id="new-rpc"
placeholder={t('newRPC')} placeholder={t('rpcURL')}
value={newRpc} value={newRpc}
onChange={e => this.setState({ newRpc: e.target.value })} onChange={e => this.setState({ newRpc: e.target.value })}
onKeyPress={e => { onKeyPress={e => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
this.validateRpc(newRpc) this.validateRpc(newRpc, chainId, ticker, nickname)
} }
}} }}
fullWidth fullWidth
margin="none" margin="dense"
/> />
<div <TextField
className="settings-tab__rpc-save-button" type="text"
id="chainid"
placeholder={t('optionalChainId')}
value={chainId}
onChange={e => this.setState({ chainId: e.target.value })}
onKeyPress={e => {
if (e.key === 'Enter') {
this.validateRpc(newRpc, chainId, ticker, nickname)
}
}}
style={{
display: this.state.showOptions ? null : 'none',
}}
fullWidth
margin="dense"
/>
<TextField
type="text"
id="ticker"
placeholder={t('optionalSymbol')}
value={ticker}
onChange={e => this.setState({ ticker: e.target.value })}
onKeyPress={e => {
if (e.key === 'Enter') {
this.validateRpc(newRpc, chainId, ticker, nickname)
}
}}
style={{
display: this.state.showOptions ? null : 'none',
}}
fullWidth
margin="dense"
/>
<TextField
type="text"
id="nickname"
placeholder={t('optionalNickname')}
value={nickname}
onChange={e => this.setState({ nickname: e.target.value })}
onKeyPress={e => {
if (e.key === 'Enter') {
this.validateRpc(newRpc, chainId, ticker, nickname)
}
}}
style={{
display: this.state.showOptions ? null : 'none',
}}
fullWidth
margin="dense"
/>
<div className="flex-row flex-align-center space-between">
<span className="settings-tab__advanced-link"
onClick={e => {
e.preventDefault()
this.setState({ showOptions: !this.state.showOptions })
}}
>
{ t(this.state.showOptions ? 'hideAdvancedOptions' : 'showAdvancedOptions') }
</span>
<button
className="button btn-primary settings-tab__rpc-save-button"
onClick={e => { onClick={e => {
e.preventDefault() e.preventDefault()
this.validateRpc(newRpc) this.validateRpc(newRpc, chainId, ticker, nickname)
}} }}
> >
{ t('save') } { t('save') }
</button>
</div> </div>
</div> </div>
</div> </div>
@ -159,11 +225,11 @@ export default class SettingsTab extends PureComponent {
) )
} }
validateRpc (newRpc) { validateRpc (newRpc, chainId, ticker = 'ETH', nickname) {
const { setRpcTarget, displayWarning } = this.props const { setRpcTarget, displayWarning } = this.props
if (validUrl.isWebUri(newRpc)) { if (validUrl.isWebUri(newRpc)) {
setRpcTarget(newRpc) setRpcTarget(newRpc, chainId, ticker, nickname)
} else { } else {
const appendedRpc = `http://${newRpc}` const appendedRpc = `http://${newRpc}`
@ -341,9 +407,13 @@ export default class SettingsTab extends PureComponent {
) )
} }
renderUseEthAsPrimaryCurrency () { renderUsePrimaryCurrencyOptions () {
const { t } = this.context const { t } = this.context
const { useETHAsPrimaryCurrency, setUseETHAsPrimaryCurrencyPreference } = this.props const {
nativeCurrency,
setUseNativeCurrencyAsPrimaryCurrencyPreference,
useNativeCurrencyAsPrimaryCurrency,
} = this.props
return ( return (
<div className="settings-page__content-row"> <div className="settings-page__content-row">
@ -359,23 +429,23 @@ export default class SettingsTab extends PureComponent {
<div className="settings-tab__radio-button"> <div className="settings-tab__radio-button">
<input <input
type="radio" type="radio"
id="eth-primary-currency" id="native-primary-currency"
onChange={() => setUseETHAsPrimaryCurrencyPreference(true)} onChange={() => setUseNativeCurrencyAsPrimaryCurrencyPreference(true)}
checked={Boolean(useETHAsPrimaryCurrency)} checked={Boolean(useNativeCurrencyAsPrimaryCurrency)}
/> />
<label <label
htmlFor="eth-primary-currency" htmlFor="native-primary-currency"
className="settings-tab__radio-label" className="settings-tab__radio-label"
> >
{ t('eth') } { nativeCurrency }
</label> </label>
</div> </div>
<div className="settings-tab__radio-button"> <div className="settings-tab__radio-button">
<input <input
type="radio" type="radio"
id="fiat-primary-currency" id="fiat-primary-currency"
onChange={() => setUseETHAsPrimaryCurrencyPreference(false)} onChange={() => setUseNativeCurrencyAsPrimaryCurrencyPreference(false)}
checked={!useETHAsPrimaryCurrency} checked={!useNativeCurrencyAsPrimaryCurrency}
/> />
<label <label
htmlFor="fiat-primary-currency" htmlFor="fiat-primary-currency"
@ -398,7 +468,7 @@ export default class SettingsTab extends PureComponent {
<div className="settings-page__content"> <div className="settings-page__content">
{ warning && <div className="settings-tab__error">{ warning }</div> } { warning && <div className="settings-tab__error">{ warning }</div> }
{ this.renderCurrentConversion() } { this.renderCurrentConversion() }
{ this.renderUseEthAsPrimaryCurrency() } { this.renderUsePrimaryCurrencyOptions() }
{ this.renderCurrentLocale() } { this.renderCurrentLocale() }
{ this.renderNewRpcUrl() } { this.renderNewRpcUrl() }
{ this.renderStateLogs() } { this.renderStateLogs() }

@ -11,7 +11,7 @@ import {
updateCurrentLocale, updateCurrentLocale,
setFeatureFlag, setFeatureFlag,
showModal, showModal,
setUseETHAsPrimaryCurrencyPreference, setUseNativeCurrencyAsPrimaryCurrencyPreference,
} from '../../../../actions' } from '../../../../actions'
import { preferencesSelector } from '../../../../selectors' import { preferencesSelector } from '../../../../selectors'
@ -20,13 +20,14 @@ const mapStateToProps = state => {
const { const {
currentCurrency, currentCurrency,
conversionDate, conversionDate,
nativeCurrency,
useBlockie, useBlockie,
featureFlags: { sendHexData } = {}, featureFlags: { sendHexData } = {},
provider = {}, provider = {},
isMascara, isMascara,
currentLocale, currentLocale,
} = metamask } = metamask
const { useETHAsPrimaryCurrency } = preferencesSelector(state) const { useNativeCurrencyAsPrimaryCurrency } = preferencesSelector(state)
return { return {
warning, warning,
@ -34,17 +35,18 @@ const mapStateToProps = state => {
currentLocale, currentLocale,
currentCurrency, currentCurrency,
conversionDate, conversionDate,
nativeCurrency,
useBlockie, useBlockie,
sendHexData, sendHexData,
provider, provider,
useETHAsPrimaryCurrency, useNativeCurrencyAsPrimaryCurrency,
} }
} }
const mapDispatchToProps = dispatch => { const mapDispatchToProps = dispatch => {
return { return {
setCurrentCurrency: currency => dispatch(setCurrentCurrency(currency)), setCurrentCurrency: currency => dispatch(setCurrentCurrency(currency)),
setRpcTarget: newRpc => dispatch(setRpcTarget(newRpc)), setRpcTarget: (newRpc, chainId, ticker, nickname) => dispatch(setRpcTarget(newRpc, chainId, ticker, nickname)),
displayWarning: warning => dispatch(displayWarning(warning)), displayWarning: warning => dispatch(displayWarning(warning)),
revealSeedConfirmation: () => dispatch(revealSeedConfirmation()), revealSeedConfirmation: () => dispatch(revealSeedConfirmation()),
setUseBlockie: value => dispatch(setUseBlockie(value)), setUseBlockie: value => dispatch(setUseBlockie(value)),
@ -54,8 +56,8 @@ const mapDispatchToProps = dispatch => {
}, },
setHexDataFeatureFlag: shouldShow => dispatch(setFeatureFlag('sendHexData', shouldShow)), setHexDataFeatureFlag: shouldShow => dispatch(setFeatureFlag('sendHexData', shouldShow)),
showResetAccountConfirmationModal: () => dispatch(showModal({ name: 'CONFIRM_RESET_ACCOUNT' })), showResetAccountConfirmationModal: () => dispatch(showModal({ name: 'CONFIRM_RESET_ACCOUNT' })),
setUseETHAsPrimaryCurrencyPreference: value => { setUseNativeCurrencyAsPrimaryCurrencyPreference: value => {
return dispatch(setUseETHAsPrimaryCurrencyPreference(value)) return dispatch(setUseNativeCurrencyAsPrimaryCurrencyPreference(value))
}, },
} }
} }

@ -2,6 +2,7 @@ import { connect } from 'react-redux'
import { import {
getConversionRate, getConversionRate,
getCurrentCurrency, getCurrentCurrency,
getNativeCurrency,
} from '../send.selectors.js' } from '../send.selectors.js'
import AccountListItem from './account-list-item.component' import AccountListItem from './account-list-item.component'
@ -11,5 +12,6 @@ function mapStateToProps (state) {
return { return {
conversionRate: getConversionRate(state), conversionRate: getConversionRate(state),
currentCurrency: getCurrentCurrency(state), currentCurrency: getCurrentCurrency(state),
nativeCurrency: getNativeCurrency(state),
} }
} }

@ -28,6 +28,7 @@ describe('AccountListItem Component', function () {
className={'mockClassName'} className={'mockClassName'}
conversionRate={4} conversionRate={4}
currentCurrency={'mockCurrentyCurrency'} currentCurrency={'mockCurrentyCurrency'}
nativeCurrency={'ETH'}
displayAddress={false} displayAddress={false}
displayBalance={false} displayBalance={false}
handleClick={propsMethodSpies.handleClick} handleClick={propsMethodSpies.handleClick}

@ -13,6 +13,7 @@ proxyquire('../account-list-item.container.js', {
'../send.selectors.js': { '../send.selectors.js': {
getConversionRate: (s) => `mockConversionRate:${s}`, getConversionRate: (s) => `mockConversionRate:${s}`,
getCurrentCurrency: (s) => `mockCurrentCurrency:${s}`, getCurrentCurrency: (s) => `mockCurrentCurrency:${s}`,
getNativeCurrency: (s) => `mockNativeCurrency:${s}`,
}, },
}) })
@ -24,6 +25,7 @@ describe('account-list-item container', () => {
assert.deepEqual(mapStateToProps('mockState'), { assert.deepEqual(mapStateToProps('mockState'), {
conversionRate: 'mockConversionRate:mockState', conversionRate: 'mockConversionRate:mockState',
currentCurrency: 'mockCurrentCurrency:mockState', currentCurrency: 'mockCurrentCurrency:mockState',
nativeCurrency: 'mockNativeCurrency:mockState',
}) })
}) })

@ -151,7 +151,6 @@ describe('SendAmountRow Component', function () {
}) })
it('should render a UserPreferencedTokenInput as the second child of the SendRowWrapper', () => { 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)) assert(wrapper.find(SendRowWrapper).childAt(1).is(UserPreferencedTokenInput))
}) })

@ -19,6 +19,7 @@ const selectors = {
getCurrentNetwork, getCurrentNetwork,
getCurrentViewContext, getCurrentViewContext,
getForceGasMin, getForceGasMin,
getNativeCurrency,
getGasLimit, getGasLimit,
getGasPrice, getGasPrice,
getGasPriceFromRecentBlocks, getGasPriceFromRecentBlocks,
@ -111,6 +112,10 @@ function getCurrentCurrency (state) {
return state.metamask.currentCurrency return state.metamask.currentCurrency
} }
function getNativeCurrency (state) {
return state.metamask.nativeCurrency
}
function getCurrentNetwork (state) { function getCurrentNetwork (state) {
return state.metamask.network return state.metamask.network
} }

@ -26,6 +26,7 @@ module.exports = {
'currentCurrency': 'USD', 'currentCurrency': 'USD',
'conversionRate': 1200.88200327, 'conversionRate': 1200.88200327,
'conversionDate': 1489013762, 'conversionDate': 1489013762,
'nativeCurrency': 'ETH',
'noActiveNotices': true, 'noActiveNotices': true,
'frequentRpcList': [], 'frequentRpcList': [],
'network': '3', 'network': '3',

@ -12,6 +12,7 @@ const {
getCurrentCurrency, getCurrentCurrency,
getCurrentNetwork, getCurrentNetwork,
getCurrentViewContext, getCurrentViewContext,
getNativeCurrency,
getForceGasMin, getForceGasMin,
getGasLimit, getGasLimit,
getGasPrice, getGasPrice,
@ -178,6 +179,15 @@ describe('send selectors', () => {
}) })
}) })
describe('getNativeCurrency()', () => {
it('should return the ticker symbol of the selected network', () => {
assert.equal(
getNativeCurrency(mockState),
'ETH'
)
})
})
describe('getCurrentNetwork()', () => { describe('getCurrentNetwork()', () => {
it('should return the id of the currently selected network', () => { it('should return the id of the currently selected network', () => {
assert.equal( assert.equal(

@ -52,12 +52,12 @@ ShiftListItem.prototype.render = function () {
}, },
}, [ }, [
h('img', { h('img', {
src: 'https://info.shapeshift.io/sites/default/files/logo.png', src: 'https://shapeshift.io/logo.png',
style: { style: {
height: '35px', height: '35px',
width: '132px', width: '132px',
position: 'absolute', position: 'absolute',
clip: 'rect(0px,23px,34px,0px)', clip: 'rect(0px,30px,34px,0px)',
}, },
}), }),
]), ]),
@ -132,7 +132,6 @@ ShiftListItem.prototype.renderInfo = function () {
case 'no_deposits': case 'no_deposits':
return h('.flex-column', { return h('.flex-column', {
style: { style: {
width: '200px',
overflow: 'hidden', overflow: 'hidden',
}, },
}, [ }, [

@ -2,7 +2,7 @@ const Component = require('react').Component
const PropTypes = require('prop-types') const PropTypes = require('prop-types')
const h = require('react-hyperscript') const h = require('react-hyperscript')
const inherits = require('util').inherits const inherits = require('util').inherits
const Identicon = require('./identicon') import Identicon from './identicon'
const connect = require('react-redux').connect const connect = require('react-redux').connect
const ethUtil = require('ethereumjs-util') const ethUtil = require('ethereumjs-util')
const classnames = require('classnames') const classnames = require('classnames')

@ -2,7 +2,7 @@ const Component = require('react').Component
const h = require('react-hyperscript') const h = require('react-hyperscript')
const inherits = require('util').inherits const inherits = require('util').inherits
const connect = require('react-redux').connect const connect = require('react-redux').connect
const Identicon = require('./identicon') import Identicon from './identicon'
const prefixForNetwork = require('../../lib/etherscan-prefix-for-network') const prefixForNetwork = require('../../lib/etherscan-prefix-for-network')
const selectors = require('../selectors') const selectors = require('../selectors')
const actions = require('../actions') const actions = require('../actions')

@ -18,10 +18,11 @@ describe('TransactionActivityLog container', () => {
const mockState = { const mockState = {
metamask: { metamask: {
conversionRate: 280.45, conversionRate: 280.45,
nativeCurrency: 'ETH',
}, },
} }
assert.deepEqual(mapStateToProps(mockState), { conversionRate: 280.45 }) assert.deepEqual(mapStateToProps(mockState), { conversionRate: 280.45, nativeCurrency: 'ETH' })
}) })
}) })
}) })

@ -4,7 +4,6 @@ import classnames from 'classnames'
import { getActivities } from './transaction-activity-log.util' import { getActivities } from './transaction-activity-log.util'
import Card from '../card' import Card from '../card'
import { getEthConversionFromWeiHex, getValueFromWeiHex } from '../../helpers/conversions.util' import { getEthConversionFromWeiHex, getValueFromWeiHex } from '../../helpers/conversions.util'
import { ETH } from '../../constants/common'
import { formatDate } from '../../util' import { formatDate } from '../../util'
export default class TransactionActivityLog extends PureComponent { export default class TransactionActivityLog extends PureComponent {
@ -16,6 +15,7 @@ export default class TransactionActivityLog extends PureComponent {
transaction: PropTypes.object, transaction: PropTypes.object,
className: PropTypes.string, className: PropTypes.string,
conversionRate: PropTypes.number, conversionRate: PropTypes.number,
nativeCurrency: PropTypes.string,
} }
state = { state = {
@ -27,10 +27,14 @@ export default class TransactionActivityLog extends PureComponent {
} }
componentDidUpdate (prevProps) { componentDidUpdate (prevProps) {
const { transaction: { history: prevHistory = [] } = {} } = prevProps const {
const { transaction: { history = [] } = {} } = this.props transaction: { history: prevHistory = [], txReceipt: { status: prevStatus } = {} } = {},
} = prevProps
const {
transaction: { history = [], txReceipt: { status } = {} } = {},
} = this.props
if (prevHistory.length !== history.length) { if (prevHistory.length !== history.length || prevStatus !== status) {
this.setActivites() this.setActivites()
} }
} }
@ -41,16 +45,17 @@ export default class TransactionActivityLog extends PureComponent {
} }
renderActivity (activity, index) { renderActivity (activity, index) {
const { conversionRate } = this.props const { conversionRate, nativeCurrency } = this.props
const { eventKey, value, timestamp } = activity const { eventKey, value, timestamp } = activity
const ethValue = index === 0 const ethValue = index === 0
? `${getValueFromWeiHex({ ? `${getValueFromWeiHex({
value, value,
toCurrency: ETH, fromCurrency: nativeCurrency,
toCurrency: nativeCurrency,
conversionRate, conversionRate,
numberOfDecimals: 6, numberOfDecimals: 6,
})} ${ETH}` })} ${nativeCurrency}`
: getEthConversionFromWeiHex({ value, toCurrency: ETH, conversionRate }) : getEthConversionFromWeiHex({ value, fromCurrency: nativeCurrency, conversionRate })
const formattedTimestamp = formatDate(timestamp) const formattedTimestamp = formatDate(timestamp)
const activityText = this.context.t(eventKey, [ethValue, formattedTimestamp]) const activityText = this.context.t(eventKey, [ethValue, formattedTimestamp])

@ -1,10 +1,11 @@
import { connect } from 'react-redux' import { connect } from 'react-redux'
import TransactionActivityLog from './transaction-activity-log.component' import TransactionActivityLog from './transaction-activity-log.component'
import { conversionRateSelector } from '../../selectors' import { conversionRateSelector, getNativeCurrency } from '../../selectors'
const mapStateToProps = state => { const mapStateToProps = state => {
return { return {
conversionRate: conversionRateSelector(state), conversionRate: conversionRateSelector(state),
nativeCurrency: getNativeCurrency(state),
} }
} }

@ -18,6 +18,7 @@ const TRANSACTION_SUBMITTED_EVENT = 'transactionSubmitted'
const TRANSACTION_CONFIRMED_EVENT = 'transactionConfirmed' const TRANSACTION_CONFIRMED_EVENT = 'transactionConfirmed'
const TRANSACTION_DROPPED_EVENT = 'transactionDropped' const TRANSACTION_DROPPED_EVENT = 'transactionDropped'
const TRANSACTION_UPDATED_EVENT = 'transactionUpdated' const TRANSACTION_UPDATED_EVENT = 'transactionUpdated'
const TRANSACTION_ERRORED_EVENT = 'transactionErrored'
const eventPathsHash = { const eventPathsHash = {
[STATUS_PATH]: true, [STATUS_PATH]: true,
@ -39,9 +40,9 @@ function eventCreator (eventKey, timestamp, value) {
} }
export function getActivities (transaction) { export function getActivities (transaction) {
const { history = [] } = transaction const { history = [], txReceipt: { status } = {} } = transaction
return history.reduce((acc, base) => { const historyActivities = history.reduce((acc, base) => {
// First history item should be transaction creation // First history item should be transaction creation
if (!Array.isArray(base) && base.status === UNAPPROVED_STATUS && base.txParams) { if (!Array.isArray(base) && base.status === UNAPPROVED_STATUS && base.txParams) {
const { time, txParams: { value } = {} } = base const { time, txParams: { value } = {} } = base
@ -83,4 +84,10 @@ export function getActivities (transaction) {
return acc return acc
}, []) }, [])
// If txReceipt.status is '0x0', that means that an on-chain error occured for the transaction,
// so we add an error entry to the Activity Log.
return status === '0x0'
? historyActivities.concat(eventCreator(TRANSACTION_ERRORED_EVENT))
: historyActivities
} }

@ -1 +1 @@
export { default } from './transaction-breakdown.component' export { default } from './transaction-breakdown.container'

@ -6,7 +6,7 @@ import Card from '../card'
import CurrencyDisplay from '../currency-display' import CurrencyDisplay from '../currency-display'
import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display' import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display'
import HexToDecimal from '../hex-to-decimal' import HexToDecimal from '../hex-to-decimal'
import { ETH, GWEI, PRIMARY, SECONDARY } from '../../constants/common' import { GWEI, PRIMARY, SECONDARY } from '../../constants/common'
import { getHexGasTotal } from '../../helpers/confirm-transaction/util' import { getHexGasTotal } from '../../helpers/confirm-transaction/util'
import { sumHexes } from '../../helpers/transactions.util' import { sumHexes } from '../../helpers/transactions.util'
@ -18,6 +18,7 @@ export default class TransactionBreakdown extends PureComponent {
static propTypes = { static propTypes = {
transaction: PropTypes.object, transaction: PropTypes.object,
className: PropTypes.string, className: PropTypes.string,
nativeCurrency: PropTypes.string.isRequired,
} }
static defaultProps = { static defaultProps = {
@ -26,7 +27,7 @@ export default class TransactionBreakdown extends PureComponent {
render () { render () {
const { t } = this.context const { t } = this.context
const { transaction, className } = this.props const { transaction, className, nativeCurrency } = this.props
const { txParams: { gas, gasPrice, value } = {}, txReceipt: { gasUsed } = {} } = transaction const { txParams: { gas, gasPrice, value } = {}, txReceipt: { gasUsed } = {} } = transaction
const gasLimit = typeof gasUsed === 'string' ? gasUsed : gas const gasLimit = typeof gasUsed === 'string' ? gasUsed : gas
@ -72,7 +73,7 @@ export default class TransactionBreakdown extends PureComponent {
<TransactionBreakdownRow title={t('gasPrice')}> <TransactionBreakdownRow title={t('gasPrice')}>
<CurrencyDisplay <CurrencyDisplay
className="transaction-breakdown__value" className="transaction-breakdown__value"
currency={ETH} currency={nativeCurrency}
denomination={GWEI} denomination={GWEI}
value={gasPrice} value={gasPrice}
hideLabel hideLabel

@ -0,0 +1,11 @@
import { connect } from 'react-redux'
import TransactionBreakdown from './transaction-breakdown.component'
import { getNativeCurrency } from '../../selectors'
const mapStateToProps = (state) => {
return {
nativeCurrency: getNativeCurrency(state),
}
}
export default connect(mapStateToProps)(TransactionBreakdown)

@ -85,6 +85,7 @@
text-align: end; text-align: end;
grid-area: primary-amount; grid-area: primary-amount;
align-self: end; align-self: end;
justify-self: end;
@media screen and (max-width: $break-small) { @media screen and (max-width: $break-small) {
padding-bottom: 2px; padding-bottom: 2px;
@ -97,6 +98,7 @@
color: #5e6064; color: #5e6064;
grid-area: secondary-amount; grid-area: secondary-amount;
align-self: start; align-self: start;
justify-self: end;
} }
} }

@ -10,7 +10,6 @@ import TransactionListItemDetails from '../transaction-list-item-details'
import { CONFIRM_TRANSACTION_ROUTE } from '../../routes' import { CONFIRM_TRANSACTION_ROUTE } from '../../routes'
import { UNAPPROVED_STATUS, TOKEN_METHOD_TRANSFER } from '../../constants/transactions' import { UNAPPROVED_STATUS, TOKEN_METHOD_TRANSFER } from '../../constants/transactions'
import { PRIMARY, SECONDARY } from '../../constants/common' import { PRIMARY, SECONDARY } from '../../constants/common'
import { ENVIRONMENT_TYPE_FULLSCREEN } from '../../../../app/scripts/lib/enums'
import { getStatusKey } from '../../helpers/transactions.util' import { getStatusKey } from '../../helpers/transactions.util'
export default class TransactionListItem extends PureComponent { export default class TransactionListItem extends PureComponent {
@ -24,7 +23,6 @@ export default class TransactionListItem extends PureComponent {
showCancelModal: PropTypes.func, showCancelModal: PropTypes.func,
showCancel: PropTypes.bool, showCancel: PropTypes.bool,
showRetry: PropTypes.bool, showRetry: PropTypes.bool,
showTransactionDetailsModal: PropTypes.func,
token: PropTypes.object, token: PropTypes.object,
tokenData: PropTypes.object, tokenData: PropTypes.object,
transaction: PropTypes.object, transaction: PropTypes.object,
@ -39,31 +37,16 @@ export default class TransactionListItem extends PureComponent {
const { const {
transaction, transaction,
history, history,
showTransactionDetailsModal,
methodData,
showCancel,
showRetry,
} = this.props } = this.props
const { id, status } = transaction const { id, status } = transaction
const { showTransactionDetails } = this.state const { showTransactionDetails } = this.state
const windowType = window.METAMASK_UI_TYPE
if (status === UNAPPROVED_STATUS) { if (status === UNAPPROVED_STATUS) {
history.push(`${CONFIRM_TRANSACTION_ROUTE}/${id}`) history.push(`${CONFIRM_TRANSACTION_ROUTE}/${id}`)
return return
} }
if (windowType === ENVIRONMENT_TYPE_FULLSCREEN) {
this.setState({ showTransactionDetails: !showTransactionDetails }) this.setState({ showTransactionDetails: !showTransactionDetails })
} else {
showTransactionDetailsModal({
transaction,
onRetry: this.handleRetry,
showRetry: showRetry && methodData.done,
onCancel: this.handleCancel,
showCancel,
})
}
} }
handleCancel = () => { handleCancel = () => {

@ -28,16 +28,6 @@ const mapDispatchToProps = dispatch => {
showCancelModal: (transactionId, originalGasPrice) => { showCancelModal: (transactionId, originalGasPrice) => {
return dispatch(showModal({ name: 'CANCEL_TRANSACTION', transactionId, originalGasPrice })) return dispatch(showModal({ name: 'CANCEL_TRANSACTION', transactionId, originalGasPrice }))
}, },
showTransactionDetailsModal: ({ transaction, onRetry, showRetry, onCancel, showCancel }) => {
return dispatch(showModal({
name: 'TRANSACTION_DETAILS',
transaction,
onRetry,
showRetry,
onCancel,
showCancel,
}))
},
} }
} }

@ -2,9 +2,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex: 1; flex: 1;
overflow-y: hidden;
margin-top: 8px; margin-top: 8px;
border-top: 1px solid $geyser;
&__completed-transactions { &__completed-transactions {
display: flex; display: flex;
@ -26,7 +24,6 @@
&__transactions { &__transactions {
flex: 1; flex: 1;
overflow-y: auto;
} }
&__pending-transactions { &__pending-transactions {

@ -4,11 +4,13 @@
align-items: center; align-items: center;
flex: 1; flex: 1;
height: 54px; height: 54px;
min-width: 0;
&__balance { &__balance {
margin-left: 12px; margin: 0 12px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-width: 0;
@media screen and (max-width: $break-small) { @media screen and (max-width: $break-small) {
align-items: center; align-items: center;
@ -21,7 +23,8 @@
font-size: 1.5rem; font-size: 1.5rem;
@media screen and (max-width: $break-small) { @media screen and (max-width: $break-small) {
margin-bottom: 12px; margin: 12px 0;
margin-left: 0;
font-size: 1.75rem; font-size: 1.75rem;
} }
} }
@ -30,7 +33,6 @@
font-size: 1.5rem; font-size: 1.5rem;
@media screen and (max-width: $break-small) { @media screen and (max-width: $break-small) {
margin-bottom: 12px;
font-size: 1.75rem; font-size: 1.75rem;
} }
} }
@ -45,6 +47,7 @@
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
min-width: 0;
@media screen and (max-width: $break-small) { @media screen and (max-width: $break-small) {
flex-direction: column; flex-direction: column;

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

Loading…
Cancel
Save