Merge pull request from GHSA-c2xw-px2x-pr65

* Remove network config store
* Remove inline networks variable in network controller
* Re-key network controller 'rpcTarget' to 'rpcUrl'
* Require chainId in lookupNetwork, implement eth_chainId
* Require chain ID in network form
* Add alert, migrations, and tests
* Add chainId validation to addToFrequentRpcList
* Update public config state selector to match new network controller
state
* Use network enums in networks-tab.constants
* Ensure chainId in provider config is current
* Update tests
feature/default_network_editable
Erik Marks 4 years ago committed by GitHub
parent 8f3b81f67a
commit 088d4c34f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      app/_locales/am/messages.json
  2. 6
      app/_locales/ar/messages.json
  3. 6
      app/_locales/bg/messages.json
  4. 6
      app/_locales/bn/messages.json
  5. 6
      app/_locales/ca/messages.json
  6. 3
      app/_locales/cs/messages.json
  7. 6
      app/_locales/da/messages.json
  8. 3
      app/_locales/de/messages.json
  9. 6
      app/_locales/el/messages.json
  10. 33
      app/_locales/en/messages.json
  11. 6
      app/_locales/es/messages.json
  12. 6
      app/_locales/es_419/messages.json
  13. 6
      app/_locales/et/messages.json
  14. 6
      app/_locales/fa/messages.json
  15. 6
      app/_locales/fi/messages.json
  16. 6
      app/_locales/fil/messages.json
  17. 6
      app/_locales/fr/messages.json
  18. 6
      app/_locales/he/messages.json
  19. 6
      app/_locales/hi/messages.json
  20. 3
      app/_locales/hn/messages.json
  21. 6
      app/_locales/hr/messages.json
  22. 3
      app/_locales/ht/messages.json
  23. 6
      app/_locales/hu/messages.json
  24. 6
      app/_locales/id/messages.json
  25. 6
      app/_locales/it/messages.json
  26. 3
      app/_locales/ja/messages.json
  27. 6
      app/_locales/kn/messages.json
  28. 6
      app/_locales/ko/messages.json
  29. 6
      app/_locales/lt/messages.json
  30. 6
      app/_locales/lv/messages.json
  31. 6
      app/_locales/ms/messages.json
  32. 3
      app/_locales/nl/messages.json
  33. 6
      app/_locales/no/messages.json
  34. 3
      app/_locales/ph/messages.json
  35. 6
      app/_locales/pl/messages.json
  36. 3
      app/_locales/pt/messages.json
  37. 6
      app/_locales/pt_BR/messages.json
  38. 6
      app/_locales/ro/messages.json
  39. 6
      app/_locales/ru/messages.json
  40. 6
      app/_locales/sk/messages.json
  41. 6
      app/_locales/sl/messages.json
  42. 6
      app/_locales/sr/messages.json
  43. 6
      app/_locales/sv/messages.json
  44. 6
      app/_locales/sw/messages.json
  45. 3
      app/_locales/ta/messages.json
  46. 3
      app/_locales/th/messages.json
  47. 3
      app/_locales/tr/messages.json
  48. 6
      app/_locales/uk/messages.json
  49. 3
      app/_locales/vi/messages.json
  50. 6
      app/_locales/zh_CN/messages.json
  51. 6
      app/_locales/zh_TW/messages.json
  52. 3
      app/scripts/background.js
  53. 10
      app/scripts/controllers/alert.js
  54. 13
      app/scripts/controllers/network/createJsonRpcClient.js
  55. 124
      app/scripts/controllers/network/network.js
  56. 36
      app/scripts/controllers/preferences.js
  57. 20
      app/scripts/lib/select-chain-id.js
  58. 5
      app/scripts/lib/setupSentry.js
  59. 9
      app/scripts/lib/typed-message-manager.js
  60. 16
      app/scripts/lib/util.js
  61. 33
      app/scripts/metamask-controller.js
  62. 38
      app/scripts/migrations/048.js
  63. 1
      app/scripts/migrations/index.js
  64. 6
      test/e2e/fixtures/imported-account/state.json
  65. 6
      test/e2e/fixtures/localization/state.json
  66. 8
      test/e2e/fixtures/personal-sign/state.json
  67. 27
      test/e2e/metamask-ui.spec.js
  68. 6
      test/unit/actions/config_test.js
  69. 8
      test/unit/app/controllers/metamask-controller-test.js
  70. 21
      test/unit/app/controllers/network/network-controller-test.js
  71. 37
      test/unit/app/controllers/preferences-controller-test.js
  72. 79
      test/unit/app/util-test.js
  73. 2
      test/unit/localhostState.js
  74. 126
      test/unit/migrations/048-test.js
  75. 2
      test/unit/ui/app/reducers/metamask.spec.js
  76. 15
      ui/app/components/app/alerts/alerts.js
  77. 1
      ui/app/components/app/alerts/alerts.scss
  78. 1
      ui/app/components/app/alerts/invalid-custom-network-alert/index.js
  79. 97
      ui/app/components/app/alerts/invalid-custom-network-alert/invalid-custom-network-alert.js
  80. 57
      ui/app/components/app/alerts/invalid-custom-network-alert/invalid-custom-network-alert.scss
  81. 2
      ui/app/components/app/alerts/unconnected-account-alert/unconnected-account-alert.js
  82. 107
      ui/app/components/app/dropdowns/network-dropdown.js
  83. 11
      ui/app/components/app/dropdowns/tests/network-dropdown.test.js
  84. 4
      ui/app/components/app/loading-network-screen/loading-network-screen.container.js
  85. 4
      ui/app/components/app/network.js
  86. 4
      ui/app/components/ui/popover/index.scss
  87. 6
      ui/app/ducks/alerts/enums.js
  88. 3
      ui/app/ducks/alerts/index.js
  89. 51
      ui/app/ducks/alerts/invalid-custom-network.js
  90. 8
      ui/app/ducks/alerts/unconnected-account.js
  91. 3
      ui/app/ducks/index.js
  92. 6
      ui/app/ducks/metamask/metamask.js
  93. 2
      ui/app/pages/routes/routes.component.js
  94. 11
      ui/app/pages/settings/networks-tab/index.scss
  95. 66
      ui/app/pages/settings/networks-tab/network-form/network-form.component.js
  96. 48
      ui/app/pages/settings/networks-tab/networks-tab.constants.js
  97. 4
      ui/app/pages/settings/networks-tab/networks-tab.container.js
  98. 6
      ui/app/selectors/selectors.js
  99. 2
      ui/app/selectors/tests/send-selectors-test-data.js
  100. 6
      ui/index.js

@ -566,9 +566,6 @@
"invalidBlockExplorerURL": {
"message": "ልክ ያልሆነ Block Explorer ዩአርኤል"
},
"invalidInput": {
"message": "ልክ ያልሆነ ግቤት።"
},
"invalidRPC": {
"message": "ልክ ያልሆነ RPC ዩአርኤል"
},
@ -743,9 +740,6 @@
"optionalBlockExplorerUrl": {
"message": "ኤክስፕሎረር URL አግድ (አማራጭ)"
},
"optionalChainId": {
"message": "የሰንሰለት መለያ ቁጥር (አማራጭ)"
},
"optionalSymbol": {
"message": "ምልክት (አማራጭ)"
},

@ -562,9 +562,6 @@
"invalidBlockExplorerURL": {
"message": "غير صحيح Block Explorer رابط"
},
"invalidInput": {
"message": "مدخل غير صحيح."
},
"invalidRPC": {
"message": "رابط آر بي سي غير صحيح"
},
@ -739,9 +736,6 @@
"optionalBlockExplorerUrl": {
"message": "العنوان الإلكتروني لمستكشف البلوكات (اختياري)"
},
"optionalChainId": {
"message": "هوية ChainID (اختياري)"
},
"optionalSymbol": {
"message": "الرمز (اختياري)"
},

@ -562,9 +562,6 @@
"invalidBlockExplorerURL": {
"message": "Невалиден Block Explorer URL адрес"
},
"invalidInput": {
"message": "Невалидно въвеждане."
},
"invalidRPC": {
"message": "Невалиден RPC URL адрес"
},
@ -742,9 +739,6 @@
"optionalBlockExplorerUrl": {
"message": "Блокиране на Explorer URL (по избор)"
},
"optionalChainId": {
"message": "ChainID (по избор)"
},
"optionalSymbol": {
"message": "Символ (по избор)"
},

@ -566,9 +566,6 @@
"invalidBlockExplorerURL": {
"message": "অবধ Block Explorer URL"
},
"invalidInput": {
"message": "অবধ ইনপট"
},
"invalidRPC": {
"message": "অবধ RPC URL"
},
@ -746,9 +743,6 @@
"optionalBlockExplorerUrl": {
"message": "একসপর URL বলক করন (ঐচিক)"
},
"optionalChainId": {
"message": "ChainID (ঐচিক)"
},
"optionalSymbol": {
"message": "পরতক (ঐচিক)"
},

@ -553,9 +553,6 @@
"invalidBlockExplorerURL": {
"message": "URL de Block Explorer"
},
"invalidInput": {
"message": "Entrada no vàlida."
},
"invalidRPC": {
"message": "URL de RPC"
},
@ -730,9 +727,6 @@
"optionalBlockExplorerUrl": {
"message": "Bloqueja l'URL d'Explorer (opcional)"
},
"optionalChainId": {
"message": "Cadena ID (opcional)"
},
"optionalSymbol": {
"message": "Símbol (opcional)"
},

@ -222,9 +222,6 @@
"invalidBlockExplorerURL": {
"message": "Neplatné Block Explorer URI"
},
"invalidInput": {
"message": "Neplatný vstup."
},
"invalidRPC": {
"message": "Neplatné RPC URI"
},

@ -559,9 +559,6 @@
"invalidBlockExplorerURL": {
"message": "Ugyldig Block Explorer-webadresse"
},
"invalidInput": {
"message": "Ugyldigt input."
},
"invalidRPC": {
"message": "Ugyldig RPC-webadresse"
},
@ -730,9 +727,6 @@
"optionalBlockExplorerUrl": {
"message": "Blok-stifinder-URL (valgfrit)"
},
"optionalChainId": {
"message": "KædeID (valgfrit)"
},
"optionalSymbol": {
"message": "Symbol (valgfrit)"
},

@ -554,9 +554,6 @@
"invalidBlockExplorerURL": {
"message": "Ungültige Block Explorer URI"
},
"invalidInput": {
"message": "Ungültige Eingabe."
},
"invalidRPC": {
"message": "Ungültige RPC URI"
},

@ -563,9 +563,6 @@
"invalidBlockExplorerURL": {
"message": "Μη έγκυρο Block Explorer URL"
},
"invalidInput": {
"message": "Μη έγκυρη είσοδος."
},
"invalidRPC": {
"message": "Μη έγκυρο RPC URL"
},
@ -743,9 +740,6 @@
"optionalBlockExplorerUrl": {
"message": "Διεύθυνση URL Εξερευνητή Μπλοκ (προαιρετικό)"
},
"optionalChainId": {
"message": "Ταυτότητα Αλυσίδας (προαιρετικό)"
},
"optionalSymbol": {
"message": "Σύμβολο (προαιρετικό)"
},

@ -823,12 +823,35 @@
"invalidBlockExplorerURL": {
"message": "Invalid Block Explorer URL"
},
"invalidInput": {
"message": "Invalid input."
"invalidCustomNetworkAlertContent1": {
"message": "The custom network '$1' is missing its chain ID.",
"description": "$1 is the name/identifier of the network."
},
"invalidCustomNetworkAlertContent2": {
"message": "To protect you from malicious or faulty network providers, chain IDs are now required for all custom networks."
},
"invalidCustomNetworkAlertContent3": {
"message": "Go to Settings > Network and enter the chain ID. You can find the chain IDs of most popular networks on $1.",
"description": "$1 is a link to https://chainid.network"
},
"invalidCustomNetworkAlertTitle": {
"message": "Invalid Custom Network"
},
"invalidHexNumber": {
"message": "Invalid hexadecimal number."
},
"invalidHexNumberLeadingZeros": {
"message": "Invalid hexadecimal number. Remove any leading zeros."
},
"invalidIpfsGateway": {
"message": "Invalid IPFS Gateway: The value must be a valid URL"
},
"invalidNumber": {
"message": "Invalid number. Enter a decimal or hexadecimal number."
},
"invalidNumberLeadingZeros": {
"message": "Invalid number. Remove any leading zeros."
},
"invalidRPC": {
"message": "Invalid RPC URL"
},
@ -951,6 +974,9 @@
"networkName": {
"message": "Network Name"
},
"networkSettingsChainIdDescription": {
"message": "The chain ID is used for signing transactions. Enter a decimal or hexadecimal number starting with '0x'."
},
"networkSettingsDescription": {
"message": "Add and edit custom RPC networks"
},
@ -1062,9 +1088,6 @@
"optionalBlockExplorerUrl": {
"message": "Block Explorer URL (optional)"
},
"optionalChainId": {
"message": "ChainID (optional)"
},
"optionalSymbol": {
"message": "Symbol (optional)"
},

@ -463,9 +463,6 @@
"invalidBlockExplorerURL": {
"message": "Invalida URL del Block Explorer"
},
"invalidInput": {
"message": "Entrada inválida"
},
"invalidRPC": {
"message": "Invalida URL del RPC"
},
@ -586,9 +583,6 @@
"ofTextNofM": {
"message": "de"
},
"optionalChainId": {
"message": "ChainID (opcional)"
},
"optionalSymbol": {
"message": "Símbolo (opcional)"
},

@ -557,9 +557,6 @@
"invalidBlockExplorerURL": {
"message": "Block Explorer de URL no válido"
},
"invalidInput": {
"message": "Ingreso no válido."
},
"invalidRPC": {
"message": "RPC de URL no válido"
},
@ -731,9 +728,6 @@
"optionalBlockExplorerUrl": {
"message": "Bloquear la URL de Explorer (opcional)"
},
"optionalChainId": {
"message": "ID de cadena (opcional)"
},
"optionalSymbol": {
"message": "Símbolo (opcional)"
},

@ -562,9 +562,6 @@
"invalidBlockExplorerURL": {
"message": "Vale Block Explorer URL"
},
"invalidInput": {
"message": "Vigane sisend."
},
"invalidRPC": {
"message": "Vale RPC URL"
},
@ -736,9 +733,6 @@
"optionalBlockExplorerUrl": {
"message": "Blokeeri Exploreri URL (valikuline)"
},
"optionalChainId": {
"message": "ChainID (valikuline)"
},
"optionalSymbol": {
"message": "Sümbol (valikuline)"
},

@ -566,9 +566,6 @@
"invalidBlockExplorerURL": {
"message": "Block Explorer URL نا معتبر"
},
"invalidInput": {
"message": "ورودی نامعتبر."
},
"invalidRPC": {
"message": "RPC URL نا معتبر"
},
@ -746,9 +743,6 @@
"optionalBlockExplorerUrl": {
"message": "بلاک کردن مرورگر URL (انتخابی)"
},
"optionalChainId": {
"message": "آی دی زنجیره (انتخابی)"
},
"optionalSymbol": {
"message": "سمبول (انتخابی)"
},

@ -566,9 +566,6 @@
"invalidBlockExplorerURL": {
"message": "Virheellinen Block Explorer URL-osoite"
},
"invalidInput": {
"message": "Virheellinen syötetty arvo."
},
"invalidRPC": {
"message": "Virheellinen RPC:n URL-osoite"
},
@ -743,9 +740,6 @@
"optionalBlockExplorerUrl": {
"message": "Estä Explorerin URL-osoite (valinnainen)"
},
"optionalChainId": {
"message": "Ketjun tunnus (valinnainen)"
},
"optionalSymbol": {
"message": "Symboli (valinnainen)"
},

@ -523,9 +523,6 @@
"invalidBlockExplorerURL": {
"message": "Hindi valid ang Block Explorer URL"
},
"invalidInput": {
"message": "Hindi valid ang input."
},
"invalidRPC": {
"message": "Hindi valid ang RPC URL"
},
@ -677,9 +674,6 @@
"optionalBlockExplorerUrl": {
"message": "Block Explorer URL (opsyonal)"
},
"optionalChainId": {
"message": "ChainID (opsyonal)"
},
"optionalSymbol": {
"message": "Simbolo (opsyonal)"
},

@ -551,9 +551,6 @@
"invalidBlockExplorerURL": {
"message": "URL Block Explorer invalide"
},
"invalidInput": {
"message": "Saisie non valide."
},
"invalidIpfsGateway": {
"message": "IPFS Gateway Invalide: la valeur doit être une URL valide"
},
@ -728,9 +725,6 @@
"optionalBlockExplorerUrl": {
"message": "Bloquer l'URL de l'explorateur (facultatif)"
},
"optionalChainId": {
"message": "ChainID (facultatif)"
},
"optionalSymbol": {
"message": "Symbole (facultatif)"
},

@ -566,9 +566,6 @@
"invalidBlockExplorerURL": {
"message": "כתובת URL לא חוקית של Block Explorer"
},
"invalidInput": {
"message": "קלט לא תקין."
},
"invalidRPC": {
"message": "כתובת URL לא חוקית של RPC"
},
@ -743,9 +740,6 @@
"optionalBlockExplorerUrl": {
"message": "חסום כתובת URL של אקספלורר (אופציונלי)"
},
"optionalChainId": {
"message": "ChainID (אופציונלי)"
},
"optionalSymbol": {
"message": "סמל (אופציונלי)"
},

@ -566,9 +566,6 @@
"invalidBlockExplorerURL": {
"message": "अमय Block Explorer URL"
},
"invalidInput": {
"message": "अमय इनपट।"
},
"invalidRPC": {
"message": "अमय RPC URL"
},
@ -743,9 +740,6 @@
"optionalBlockExplorerUrl": {
"message": "एकसपरर यआरएल बक (वकलिक)"
},
"optionalChainId": {
"message": "चनआईड (वकलिक)"
},
"optionalSymbol": {
"message": "सिबल (वकलिक)"
},

@ -202,9 +202,6 @@
"invalidBlockExplorerURL": {
"message": "अमय Block Explorer क URI"
},
"invalidInput": {
"message": "अमय इनपट।"
},
"invalidRPC": {
"message": "अमय RPC क URI"
},

@ -562,9 +562,6 @@
"invalidBlockExplorerURL": {
"message": "Nevaljani URL Block Explorer-a"
},
"invalidInput": {
"message": "Nevaljani upis."
},
"invalidRPC": {
"message": "Nevaljani URL RPC-a"
},
@ -739,9 +736,6 @@
"optionalBlockExplorerUrl": {
"message": "Blokiraj Explorerov URL (neobavezno)"
},
"optionalChainId": {
"message": "Identifikacijska oznaka bloka (neobavezno)"
},
"optionalSymbol": {
"message": "Simbol (neobavezno)"
},

@ -325,9 +325,6 @@
"invalidBlockExplorerURL": {
"message": "Block Explorer URI pa valab"
},
"invalidInput": {
"message": "Sa ou rantre a pa valab"
},
"invalidRPC": {
"message": "RPC URI pa valab"
},

@ -562,9 +562,6 @@
"invalidBlockExplorerURL": {
"message": "Helytelen Block Explorer URL"
},
"invalidInput": {
"message": "Érvénytelen bevitel."
},
"invalidRPC": {
"message": "Helytelen RPC URL"
},
@ -739,9 +736,6 @@
"optionalBlockExplorerUrl": {
"message": "Explorer URL letiltása (nem kötelező)"
},
"optionalChainId": {
"message": "ChainID (nem kötelező)"
},
"optionalSymbol": {
"message": "Szimbólum (opcionális)"
},

@ -553,9 +553,6 @@
"invalidBlockExplorerURL": {
"message": "URL Block Explorer Tidak Sah"
},
"invalidInput": {
"message": "Input tidak sah."
},
"invalidRPC": {
"message": "URL RPC Tidak Sah"
},
@ -727,9 +724,6 @@
"optionalBlockExplorerUrl": {
"message": "Blokir URL Penjelajah (opsional)"
},
"optionalChainId": {
"message": "ID Rantai (opsional)"
},
"optionalSymbol": {
"message": "Simbol (opsional)"
},

@ -823,9 +823,6 @@
"invalidBlockExplorerURL": {
"message": "URI Block Explorer invalido"
},
"invalidInput": {
"message": "Input non valido."
},
"invalidIpfsGateway": {
"message": "Portale IPFS non valido: il valore deve essere un URL valido"
},
@ -1062,9 +1059,6 @@
"optionalBlockExplorerUrl": {
"message": "URL del Block Explorer (opzionale)"
},
"optionalChainId": {
"message": "ChainID (opzionale)"
},
"optionalSymbol": {
"message": "Simbolo (opzionale)"
},

@ -265,9 +265,6 @@
"invalidAddress": {
"message": "アドレスが無効です。"
},
"invalidInput": {
"message": "インプットが無効です。"
},
"jsonFile": {
"message": "JSONファイル",
"description": "format for importing an account"

@ -566,9 +566,6 @@
"invalidBlockExplorerURL": {
"message": "ಅಮಯವದ Block Explorer URL"
},
"invalidInput": {
"message": "ಅಮಯವದ ಇನ."
},
"invalidRPC": {
"message": "ಅಮಯವದ RPC URL"
},
@ -746,9 +743,6 @@
"optionalBlockExplorerUrl": {
"message": "ಅನಷಕ URL ಅನಿಿಿ (ಐಚಿಕ)"
},
"optionalChainId": {
"message": "ಚಐಡಿ (ಐಚಿಕ)"
},
"optionalSymbol": {
"message": "ಚಿ (ಐಚಿಕ)"
},

@ -560,9 +560,6 @@
"invalidBlockExplorerURL": {
"message": "올바르지 않은 Block Explorer URI"
},
"invalidInput": {
"message": "올바르지 않은 입력값"
},
"invalidRPC": {
"message": "올바르지 않은 RPC URI"
},
@ -740,9 +737,6 @@
"optionalBlockExplorerUrl": {
"message": "익스플로러 URL 차단 (선택 사항)"
},
"optionalChainId": {
"message": "ChainID (선택)"
},
"optionalSymbol": {
"message": "Symbol (선택)"
},

@ -566,9 +566,6 @@
"invalidBlockExplorerURL": {
"message": "Netinkamas Block Explorer URL"
},
"invalidInput": {
"message": "Netinkama įvestis."
},
"invalidRPC": {
"message": "Netinkamas RPC URL"
},
@ -746,9 +743,6 @@
"optionalBlockExplorerUrl": {
"message": "Blokuoti naršyklės URL (pasirinktinai)"
},
"optionalChainId": {
"message": "Grandinės ID (nebūtinas)"
},
"optionalSymbol": {
"message": "Simbolis (nebūtinas)"
},

@ -562,9 +562,6 @@
"invalidBlockExplorerURL": {
"message": "Nederīgs Block Explorer URL"
},
"invalidInput": {
"message": "Nederīga ievadītā vērtība."
},
"invalidRPC": {
"message": "Nederīgs RPC URL"
},
@ -742,9 +739,6 @@
"optionalBlockExplorerUrl": {
"message": "Bloķēt Explorer URL (pēc izvēles)"
},
"optionalChainId": {
"message": "ChainID (neobligāti)"
},
"optionalSymbol": {
"message": "Simbols (neobligāti)"
},

@ -549,9 +549,6 @@
"invalidBlockExplorerURL": {
"message": "URL Block Explorer tidak sah"
},
"invalidInput": {
"message": "Input tidak sah."
},
"invalidRPC": {
"message": "URL RPC tidak sah"
},
@ -720,9 +717,6 @@
"optionalBlockExplorerUrl": {
"message": "Sekat URL Explorer (pilihan)"
},
"optionalChainId": {
"message": "ChainID (pilihan)"
},
"optionalSymbol": {
"message": "Simbol (pilihan)"
},

@ -196,9 +196,6 @@
"invalidBlockExplorerURL": {
"message": "Ongeldige Block Explorer URI"
},
"invalidInput": {
"message": "Ongeldige invoer."
},
"invalidRPC": {
"message": "Ongeldige RPC-URI"
},

@ -553,9 +553,6 @@
"invalidBlockExplorerURL": {
"message": "Ugyldig Block Explorer URL"
},
"invalidInput": {
"message": "Ugyldig inndata "
},
"invalidRPC": {
"message": "Ugyldig RPC URL"
},
@ -733,9 +730,6 @@
"optionalBlockExplorerUrl": {
"message": "Blokker Explorer URL (valgfritt)"
},
"optionalChainId": {
"message": "Blokkjede (vagfritt)"
},
"optionalSymbol": {
"message": "Symbol (valgfritt)"
},

@ -154,9 +154,6 @@
"invalidAddress": {
"message": "Invalid ang address"
},
"invalidInput": {
"message": "Invalid ang input."
},
"loading": {
"message": "Naglo-load..."
},

@ -566,9 +566,6 @@
"invalidBlockExplorerURL": {
"message": "Nieprawidłowe Block Explorer URI"
},
"invalidInput": {
"message": "Nieprawidłowe dane."
},
"invalidRPC": {
"message": "Nieprawidłowe RPC URI"
},
@ -740,9 +737,6 @@
"optionalBlockExplorerUrl": {
"message": "Adres URL przeglądarki łańcucha bloków (opcjonalnie)"
},
"optionalChainId": {
"message": "Identyfikator łańcucha (opcjonalnie)"
},
"optionalSymbol": {
"message": "Symbol (opcjonalnie)"
},

@ -202,9 +202,6 @@
"invalidBlockExplorerURL": {
"message": "Block Explorer URI Inválido"
},
"invalidInput": {
"message": "Campo inválido."
},
"invalidRPC": {
"message": "RPC URI Inválido"
},

@ -560,9 +560,6 @@
"invalidBlockExplorerURL": {
"message": "URL de Block Explorer inválida"
},
"invalidInput": {
"message": "Entrada inválida."
},
"invalidRPC": {
"message": "URL de RPC inválida"
},
@ -734,9 +731,6 @@
"optionalBlockExplorerUrl": {
"message": "URL exploradora de blocos (opcional)"
},
"optionalChainId": {
"message": "ChainID (opcional)"
},
"optionalSymbol": {
"message": "Símbolo (opcional)"
},

@ -556,9 +556,6 @@
"invalidBlockExplorerURL": {
"message": "URL Block Explorer nevalid"
},
"invalidInput": {
"message": "Intrare nevalidă."
},
"invalidRPC": {
"message": "URL RPC nevalid"
},
@ -733,9 +730,6 @@
"optionalBlockExplorerUrl": {
"message": "URL explorator bloc (opțional)"
},
"optionalChainId": {
"message": "ChainID (opțional)"
},
"optionalSymbol": {
"message": "Simbol (opțional)"
},

@ -595,9 +595,6 @@
"invalidBlockExplorerURL": {
"message": "Неверный Block Explorer URI"
},
"invalidInput": {
"message": "Неверный ввод."
},
"invalidRPC": {
"message": "Неверный RPC URI"
},
@ -775,9 +772,6 @@
"optionalBlockExplorerUrl": {
"message": "URL блок-эксплорера (необязательно)"
},
"optionalChainId": {
"message": "ID сети (необязательно)"
},
"optionalSymbol": {
"message": "Символ (необязательно)"
},

@ -553,9 +553,6 @@
"invalidBlockExplorerURL": {
"message": "Neplatné Block Explorer URI"
},
"invalidInput": {
"message": "Neplatný vstup."
},
"invalidRPC": {
"message": "Neplatné RPC URI"
},
@ -715,9 +712,6 @@
"optionalBlockExplorerUrl": {
"message": "Blokovať URL Explorera (voliteľné)"
},
"optionalChainId": {
"message": "ChainID (voliteľné)"
},
"optionalSymbol": {
"message": "Symbol (voliteľné)"
},

@ -557,9 +557,6 @@
"invalidBlockExplorerURL": {
"message": "Neveljaven Block Explorer URL"
},
"invalidInput": {
"message": "Neveljaven vnos."
},
"invalidRPC": {
"message": "Neveljaven RPC URL"
},
@ -731,9 +728,6 @@
"optionalBlockExplorerUrl": {
"message": "Blokiraj URL Explorerja (poljubno)"
},
"optionalChainId": {
"message": "ChainID (nezahtevano)"
},
"optionalSymbol": {
"message": "Simbol (nezahtevano)"
},

@ -563,9 +563,6 @@
"invalidBlockExplorerURL": {
"message": "Nevažeći Block Explorer URL"
},
"invalidInput": {
"message": "Nevažeći unos."
},
"invalidRPC": {
"message": "Nevažeći RPC URL"
},
@ -737,9 +734,6 @@
"optionalBlockExplorerUrl": {
"message": "Blokirajte URL Explorer-a (opciono)"
},
"optionalChainId": {
"message": "ChainID (opciono)"
},
"optionalSymbol": {
"message": "Simbol (opciono)"
},

@ -556,9 +556,6 @@
"invalidBlockExplorerURL": {
"message": "Ogiltig Block Explorer URL"
},
"invalidInput": {
"message": "Ogiltigt input."
},
"invalidRPC": {
"message": "Ogiltig RPC-URL"
},
@ -730,9 +727,6 @@
"optionalBlockExplorerUrl": {
"message": "Block Explorer URL (valfritt)"
},
"optionalChainId": {
"message": "ChainID (frivilligt)"
},
"optionalSymbol": {
"message": "Symbol (frivillig)"
},

@ -553,9 +553,6 @@
"invalidBlockExplorerURL": {
"message": "Block Explorer URL batili"
},
"invalidInput": {
"message": "Maandii si sahihi."
},
"invalidRPC": {
"message": "RPC URL batili"
},
@ -724,9 +721,6 @@
"optionalBlockExplorerUrl": {
"message": "URL ya Block Explorer URL (hiari)"
},
"optionalChainId": {
"message": "Utambulisho wa Mnyororo (hiari)"
},
"optionalSymbol": {
"message": "Ishara (hiari)"
},

@ -256,9 +256,6 @@
"invalidBlockExplorerURL": {
"message": "தவறன Block Explorer URI"
},
"invalidInput": {
"message": "தவறன உள.."
},
"invalidRPC": {
"message": "தவறன RPC URI"
},

@ -280,9 +280,6 @@
"invalidBlockExplorerURL": {
"message": "Block Explorer URI ไมกตอง"
},
"invalidInput": {
"message": "อนพทไมกตอง"
},
"invalidRPC": {
"message": "RPC URI ไมกตอง"
},

@ -226,9 +226,6 @@
"invalidBlockExplorerURL": {
"message": "Geçersiz Block Explorer URI"
},
"invalidInput": {
"message": "Geçersiz giriş."
},
"invalidRPC": {
"message": "Geçersiz RPC URI"
},

@ -566,9 +566,6 @@
"invalidBlockExplorerURL": {
"message": "Недійсний Block Explorer URL"
},
"invalidInput": {
"message": "Неприпустимий ввід."
},
"invalidRPC": {
"message": "Недійсний RPC URL"
},
@ -746,9 +743,6 @@
"optionalBlockExplorerUrl": {
"message": "Блокувати Explorer URL (не обов'язково)"
},
"optionalChainId": {
"message": "ChainID (необов’язково)"
},
"optionalSymbol": {
"message": "Символ (не обов'язково)"
},

@ -166,9 +166,6 @@
"invalidAddress": {
"message": "Địa chỉ không hợp lệ"
},
"invalidInput": {
"message": "Thông tin nhập vào không hợp lệ"
},
"jsonFile": {
"message": "Tập tin JSON",
"description": "format for importing an account"

@ -557,9 +557,6 @@
"invalidBlockExplorerURL": {
"message": "无效 Block Explorer URI"
},
"invalidInput": {
"message": "无效输入."
},
"invalidRPC": {
"message": "无效 RPC URI"
},
@ -728,9 +725,6 @@
"optionalBlockExplorerUrl": {
"message": "屏蔽管理器 URL(选填)"
},
"optionalChainId": {
"message": "ChainID(选填)"
},
"optionalSymbol": {
"message": "符号(选填)"
},

@ -566,9 +566,6 @@
"invalidBlockExplorerURL": {
"message": "無效的區塊鏈瀏覽器 URL"
},
"invalidInput": {
"message": "輸入錯誤。"
},
"invalidRPC": {
"message": "無效的 RPC URI"
},
@ -737,9 +734,6 @@
"optionalBlockExplorerUrl": {
"message": "區塊鏈瀏覽器 URL(非必要)"
},
"optionalChainId": {
"message": "ChainID (可選)"
},
"optionalSymbol": {
"message": "Symbol (可選)"
},

@ -97,7 +97,6 @@ initialize().catch(log.error)
* @property {boolean} isInitialized - Whether the first vault has been created.
* @property {boolean} isUnlocked - Whether the vault is currently decrypted and accounts are available for selection.
* @property {boolean} isAccountMenuOpen - Represents whether the main account selection UI is currently displayed.
* @property {string} rpcTarget - DEPRECATED - The URL of the current RPC provider.
* @property {Object} identities - An object matching lower-case hex addresses to Identity objects with "address" and "name" (nickname) keys.
* @property {Object} unapprovedTxs - An object mapping transaction hashes to unapproved transactions.
* @property {Array} frequentRpcList - A list of frequently used RPCs, including custom user-provided ones.
@ -110,7 +109,7 @@ initialize().catch(log.error)
* @property {boolean} welcomeScreen - True if welcome screen should be shown.
* @property {string} currentLocale - A locale string matching the user's preferred display language.
* @property {Object} provider - The current selected network provider.
* @property {string} provider.rpcTarget - The address for the RPC API, if using an RPC API.
* @property {string} provider.rpcUrl - The address for the RPC API, if using an RPC API.
* @property {string} provider.type - An identifier for the type of network selected, allows MetaMask to use custom provider strategies for known networks.
* @property {string} network - A stringified number of the current network ID.
* @property {Object} accounts - An object mapping lower-case hex addresses to objects with "balance" and "address" keys, both storing hex string values.

@ -2,8 +2,11 @@ import ObservableStore from 'obs-store'
/**
* @typedef {Object} AlertControllerInitState
* @property {Object} alertEnabledness - A map of any alerts that were suppressed keyed by alert ID, where the value
* is the timestamp of when the user suppressed the alert.
* @property {Object} alertEnabledness - A map of alerts IDs to booleans, where
* `true` indicates that the alert is enabled and shown, and `false` the opposite.
* @property {Object} unconnectedAccountAlertShownOrigins - A map of origin
* strings to booleans indicating whether the "switch to connected" alert has
* been shown (`true`) or otherwise (`false`).
*/
/**
@ -13,6 +16,8 @@ import ObservableStore from 'obs-store'
export const ALERT_TYPES = {
unconnectedAccount: 'unconnectedAccount',
// enumerated here but has no background state
invalidCustomNetwork: 'invalidCustomNetwork',
}
const defaultState = {
@ -44,6 +49,7 @@ export default class AlertController {
...initState,
unconnectedAccountAlertShownOrigins: {},
}
this.store = new ObservableStore(state)
this.selectedAddress = preferencesStore.getState().selectedAddress

@ -7,12 +7,13 @@ import createBlockTrackerInspectorMiddleware from 'eth-json-rpc-middleware/block
import providerFromMiddleware from 'eth-json-rpc-middleware/providerFromMiddleware'
import BlockTracker from 'eth-block-tracker'
export default function createJsonRpcClient ({ rpcUrl }) {
export default function createJsonRpcClient ({ rpcUrl, chainId }) {
const fetchMiddleware = createFetchMiddleware({ rpcUrl })
const blockProvider = providerFromMiddleware(fetchMiddleware)
const blockTracker = new BlockTracker({ provider: blockProvider })
const networkMiddleware = mergeMiddleware([
createChainIdMiddleware(chainId),
createBlockRefRewriteMiddleware({ blockTracker }),
createBlockCacheMiddleware({ blockTracker }),
createInflightMiddleware(),
@ -21,3 +22,13 @@ export default function createJsonRpcClient ({ rpcUrl }) {
])
return { networkMiddleware, blockTracker }
}
function createChainIdMiddleware (chainId) {
return (req, res, next, end) => {
if (req.method === 'eth_chainId') {
res.result = chainId
return end()
}
return next()
}
}

@ -2,11 +2,11 @@ import assert from 'assert'
import EventEmitter from 'events'
import ObservableStore from 'obs-store'
import ComposedStore from 'obs-store/lib/composed'
import EthQuery from 'eth-query'
import JsonRpcEngine from 'json-rpc-engine'
import providerFromEngine from 'eth-json-rpc-middleware/providerFromEngine'
import log from 'loglevel'
import { createSwappableProxy, createEventEmitterProxy } from 'swappable-obj-proxy'
import EthQuery from 'eth-query'
import createMetamaskMiddleware from './createMetamaskMiddleware'
import createInfuraClient from './createInfuraClient'
import createJsonRpcClient from './createJsonRpcClient'
@ -17,16 +17,18 @@ import {
MAINNET,
LOCALHOST,
INFURA_PROVIDER_TYPES,
NETWORK_TYPE_TO_ID_MAP,
} from './enums'
const networks = { networkList: {} }
const env = process.env.METAMASK_ENV
const { METAMASK_DEBUG } = process.env
let defaultProviderConfigType
let defaultProviderChainId
if (process.env.IN_TEST === 'true') {
defaultProviderConfigType = LOCALHOST
// Decimal 5777, an arbitrary chain ID we use for testing
defaultProviderChainId = '0x1691'
} else if (METAMASK_DEBUG || env === 'test') {
defaultProviderConfigType = RINKEBY
} else {
@ -35,31 +37,36 @@ if (process.env.IN_TEST === 'true') {
const defaultProviderConfig = {
type: defaultProviderConfigType,
}
const defaultNetworkConfig = {
ticker: 'ETH',
}
if (defaultProviderChainId) {
defaultProviderConfig.chainId = defaultProviderChainId
}
export default class NetworkController extends EventEmitter {
constructor (opts = {}) {
super()
// parse options
const providerConfig = opts.provider || defaultProviderConfig
// create stores
this.providerStore = new ObservableStore(providerConfig)
this.providerStore = new ObservableStore(
opts.provider || { ...defaultProviderConfig },
)
this.networkStore = new ObservableStore('loading')
this.networkConfig = new ObservableStore(defaultNetworkConfig)
this.store = new ComposedStore({ provider: this.providerStore, network: this.networkStore, settings: this.networkConfig })
this.on('networkDidChange', this.lookupNetwork)
this.store = new ComposedStore({
provider: this.providerStore,
network: this.networkStore,
})
// provider and block tracker
this._provider = null
this._blockTracker = null
// provider and block tracker proxies - because the network changes
this._providerProxy = null
this._blockTrackerProxy = null
this.on('networkDidChange', this.lookupNetwork)
}
/**
@ -79,8 +86,8 @@ export default class NetworkController extends EventEmitter {
initializeProvider (providerParams) {
this._baseProviderParams = providerParams
const { type, rpcTarget, chainId, ticker, nickname } = this.providerStore.getState()
this._configureProvider({ type, rpcTarget, chainId, ticker, nickname })
const { type, rpcUrl, chainId } = this.getProviderConfig()
this._configureProvider({ type, rpcUrl, chainId })
this.lookupNetwork()
}
@ -102,21 +109,8 @@ export default class NetworkController extends EventEmitter {
return this.networkStore.getState()
}
getNetworkConfig () {
return this.networkConfig.getState()
}
setNetworkState (network, type) {
if (network === 'loading') {
this.networkStore.putState(network)
return
}
// type must be defined
if (!type) {
return
}
this.networkStore.putState(networks.networkList[type]?.chainId || network)
setNetworkState (network) {
this.networkStore.putState(network)
}
isNetworkLoading () {
@ -129,48 +123,61 @@ export default class NetworkController extends EventEmitter {
log.warn('NetworkController - lookupNetwork aborted due to missing provider')
return
}
const { type } = this.providerStore.getState()
const { type, chainId: configChainId } = this.getProviderConfig()
const chainId = NETWORK_TYPE_TO_ID_MAP[type]?.chainId || configChainId
if (!chainId) {
log.warn('NetworkController - lookupNetwork aborted due to missing chainId')
this.setNetworkState('loading')
return
}
// Ping the RPC endpoint so we can confirm that it works
const ethQuery = new EthQuery(this._provider)
const initialNetwork = this.getNetworkState()
ethQuery.sendAsync({ method: 'net_version' }, (err, network) => {
ethQuery.sendAsync({ method: 'net_version' }, (err, _networkVersion) => {
const currentNetwork = this.getNetworkState()
if (initialNetwork === currentNetwork) {
if (err) {
this.setNetworkState('loading')
return
}
log.info(`web3.getNetwork returned ${network}`)
this.setNetworkState(network, type)
// Now we set the network state to the chainId computed earlier
this.setNetworkState(chainId)
}
})
}
setRpcTarget (rpcTarget, chainId, ticker = 'ETH', nickname = '', rpcPrefs) {
const providerConfig = {
setRpcTarget (rpcUrl, chainId, ticker = 'ETH', nickname = '', rpcPrefs) {
this.setProviderConfig({
type: 'rpc',
rpcTarget,
rpcUrl,
chainId,
ticker,
nickname,
rpcPrefs,
}
this.providerConfig = providerConfig
})
}
async setProviderType (type, rpcTarget = '', ticker = 'ETH', nickname = '') {
async setProviderType (type, rpcUrl = '', ticker = 'ETH', nickname = '') {
assert.notEqual(type, 'rpc', `NetworkController - cannot call "setProviderType" with type 'rpc'. use "setRpcTarget"`)
assert(INFURA_PROVIDER_TYPES.includes(type) || type === LOCALHOST, `NetworkController - Unknown rpc type "${type}"`)
const providerConfig = { type, rpcTarget, ticker, nickname }
this.providerConfig = providerConfig
const { chainId } = NETWORK_TYPE_TO_ID_MAP[type]
this.setProviderConfig({ type, rpcUrl, chainId, ticker, nickname })
}
resetConnection () {
this.providerConfig = this.getProviderConfig()
this.setProviderConfig(this.getProviderConfig())
}
set providerConfig (providerConfig) {
this.providerStore.updateState(providerConfig)
this._switchNetwork(providerConfig)
/**
* Sets the provider config and switches the network.
*/
setProviderConfig (config) {
this.providerStore.updateState(config)
this._switchNetwork(config)
}
getProviderConfig () {
@ -187,8 +194,7 @@ export default class NetworkController extends EventEmitter {
this.emit('networkDidChange', opts.type)
}
_configureProvider (opts) {
const { type, rpcTarget, chainId, ticker, nickname } = opts
_configureProvider ({ type, rpcUrl, chainId }) {
// infura type-based endpoints
const isInfura = INFURA_PROVIDER_TYPES.includes(type)
if (isInfura) {
@ -198,7 +204,7 @@ export default class NetworkController extends EventEmitter {
this._configureLocalhostProvider()
// url-based rpc endpoints
} else if (type === 'rpc') {
this._configureStandardProvider({ rpcUrl: rpcTarget, chainId, ticker, nickname })
this._configureStandardProvider(rpcUrl, chainId)
} else {
throw new Error(`NetworkController - _configureProvider - unknown type "${type}"`)
}
@ -211,11 +217,6 @@ export default class NetworkController extends EventEmitter {
projectId,
})
this._setNetworkClient(networkClient)
// setup networkConfig
const settings = {
ticker: 'ETH',
}
this.networkConfig.putState(settings)
}
_configureLocalhostProvider () {
@ -224,22 +225,9 @@ export default class NetworkController extends EventEmitter {
this._setNetworkClient(networkClient)
}
_configureStandardProvider ({ rpcUrl, chainId, ticker, nickname }) {
_configureStandardProvider (rpcUrl, chainId) {
log.info('NetworkController - configureStandardProvider', rpcUrl)
const networkClient = createJsonRpcClient({ rpcUrl })
// hack to add a 'rpc' network with chainId
networks.networkList.rpc = {
chainId,
rpcUrl,
ticker: ticker || 'ETH',
nickname,
}
// setup networkConfig
let settings = {
network: chainId,
}
settings = Object.assign(settings, networks.networkList.rpc)
this.networkConfig.putState(settings)
const networkClient = createJsonRpcClient({ rpcUrl, chainId })
this._setNetworkClient(networkClient)
}

@ -1,6 +1,7 @@
import ObservableStore from 'obs-store'
import { normalize as normalizeAddress } from 'eth-sig-util'
import { isValidAddress, sha3, bufferToHex } from 'ethereumjs-util'
import { isPrefixedFormattedHexString } from '../lib/util'
import { addInternalMethodPrefix } from './permissions'
export default class PreferencesController {
@ -478,10 +479,8 @@ export default class PreferencesController {
* @param {string} chainId - Optional chainId of the selected network.
* @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.
*
*/
updateRpc (newRpcDetails) {
const rpcList = this.getFrequentRpcListDetail()
const index = rpcList.findIndex((element) => {
@ -494,39 +493,38 @@ export default class PreferencesController {
this.store.updateState({ frequentRpcListDetail: rpcList })
} else {
const { rpcUrl, chainId, ticker, nickname, rpcPrefs = {} } = newRpcDetails
return this.addToFrequentRpcList(rpcUrl, chainId, ticker, nickname, rpcPrefs)
this.addToFrequentRpcList(rpcUrl, chainId, ticker, nickname, rpcPrefs)
}
return Promise.resolve(rpcList)
}
/**
* Adds custom RPC url to state.
*
* @param {string} url - The RPC url to add to frequentRpcList.
* @param {string} chainId - Optional chainId of the selected network.
* @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.
* @param {string} rpcUrl - The RPC url to add to frequentRpcList.
* @param {string} chainId - The chainId of the selected network.
* @param {string} [ticker] - Ticker symbol of the selected network.
* @param {string} [nickname] - Nickname of the selected network.
*
*/
addToFrequentRpcList (url, chainId, ticker = 'ETH', nickname = '', rpcPrefs = {}) {
addToFrequentRpcList (rpcUrl, chainId, ticker = 'ETH', nickname = '', rpcPrefs = {}) {
if (rpcUrl === 'http://localhost:8545') {
return
}
const rpcList = this.getFrequentRpcListDetail()
const index = rpcList.findIndex((element) => {
return element.rpcUrl === url
return element.rpcUrl === rpcUrl
})
if (index !== -1) {
rpcList.splice(index, 1)
}
if (url !== 'http://localhost:8545') {
let checkedChainId
// eslint-disable-next-line radix
if (Boolean(chainId) && !Number.isNaN(parseInt(chainId))) {
checkedChainId = chainId
}
rpcList.push({ rpcUrl: url, chainId: checkedChainId, ticker, nickname, rpcPrefs })
if (!isPrefixedFormattedHexString(chainId)) {
throw new Error(`Invalid chainId: "${chainId}"`)
}
rpcList.push({ rpcUrl, chainId, ticker, nickname, rpcPrefs })
this.store.updateState({ frequentRpcListDetail: rpcList })
return Promise.resolve(rpcList)
}
/**

@ -1,20 +0,0 @@
import {
MAINNET_CHAIN_ID,
ROPSTEN_CHAIN_ID,
RINKEBY_CHAIN_ID,
KOVAN_CHAIN_ID,
GOERLI_CHAIN_ID,
} from '../controllers/network/enums'
const standardNetworkId = {
'1': MAINNET_CHAIN_ID,
'3': ROPSTEN_CHAIN_ID,
'4': RINKEBY_CHAIN_ID,
'42': KOVAN_CHAIN_ID,
'5': GOERLI_CHAIN_ID,
}
export default function selectChainId (metamaskState) {
const { network, provider: { chainId } } = metamaskState
return standardNetworkId[network] || `0x${parseInt(chainId, 10).toString(16)}`
}

@ -44,11 +44,6 @@ export const SENTRY_STATE = {
type: true,
},
seedPhraseBackedUp: true,
settings: {
chainId: true,
ticker: true,
nickname: true,
},
showRestorePrompt: true,
threeBoxDisabled: true,
threeBoxLastUpdated: true,

@ -166,9 +166,12 @@ export default class TypedMessageManager extends EventEmitter {
assert.ok(data.primaryType in data.types, `Primary type of "${data.primaryType}" has no type definition.`)
assert.equal(validation.errors.length, 0, 'Signing data must conform to EIP-712 schema. See https://git.io/fNtcx.')
const { chainId } = data.domain
// eslint-disable-next-line radix
const activeChainId = parseInt(this.networkController.getNetworkState())
chainId && assert.equal(chainId, activeChainId, `Provided chainId "${chainId}" must match the active chainId "${activeChainId}"`)
if (chainId) {
// eslint-disable-next-line radix
const activeChainId = parseInt(this.networkController.getNetworkState())
assert.ok(!Number.isNaN(activeChainId), `Cannot sign messages for chainId "${chainId}", because MetaMask is switching networks.`)
assert.equal(chainId, activeChainId, `Provided chainId "${chainId}" must match the active chainId "${activeChainId}"`)
}
break
}
default:

@ -149,6 +149,21 @@ function checkForError () {
return new Error(lastError.message)
}
/**
* Checks whether the given value is a 0x-prefixed, non-zero, non-zero-padded,
* hexadecimal string.
*
* @param {any} value - The value to check.
* @returns {boolean} True if the value is a correctly formatted hex string,
* false otherwise.
*/
function isPrefixedFormattedHexString (value) {
if (typeof value !== 'string') {
return false
}
return (/^0x[1-9a-f]+[0-9a-f]*$/ui).test(value)
}
export {
getPlatform,
getEnvironmentType,
@ -157,4 +172,5 @@ export {
bnToHex,
BnMultiplyByFraction,
checkForError,
isPrefixedFormattedHexString,
}

@ -54,7 +54,6 @@ import { PermissionsController } from './controllers/permissions'
import getRestrictedMethods from './controllers/permissions/restrictedMethods'
import nodeify from './lib/nodeify'
import accountImporter from './account-import-strategies'
import selectChainId from './lib/select-chain-id'
import seedPhraseVerifier from './lib/seed-phrase-verifier'
import backgroundMetaMetricsEvent from './lib/background-metametrics'
@ -376,14 +375,16 @@ export default class MetamaskController extends EventEmitter {
}
function updatePublicConfigStore (memState) {
publicConfigStore.putState(selectPublicState(memState))
if (memState.network !== 'loading') {
publicConfigStore.putState(selectPublicState(memState))
}
}
function selectPublicState ({ isUnlocked, network, provider }) {
function selectPublicState ({ isUnlocked, network }) {
return {
isUnlocked,
networkVersion: network,
chainId: selectChainId({ network, provider }),
chainId: network,
networkVersion: Number.parseInt(network, 16).toString(),
}
}
return publicConfigStore
@ -1849,7 +1850,7 @@ export default class MetamaskController extends EventEmitter {
* @param {Function} cb - A callback function returning currency info.
*/
setCurrentCurrency (currencyCode, cb) {
const { ticker } = this.networkController.getNetworkConfig()
const { ticker } = this.networkController.getProviderConfig()
try {
const currencyState = {
nativeCurrency: ticker,
@ -1866,7 +1867,6 @@ export default class MetamaskController extends EventEmitter {
}
}
// network
/**
* A method for selecting a custom URL for an ethereum RPC provider and updating it
* @param {string} rpcUrl - A URL for a valid Ethereum RPC API.
@ -1875,7 +1875,6 @@ export default class MetamaskController extends EventEmitter {
* @param {string} nickname - Optional nickname of the selected network.
* @returns {Promise<String>} - The RPC Target URL confirmed.
*/
async updateAndSetCustomRpc (rpcUrl, chainId, ticker = 'ETH', nickname, rpcPrefs) {
await this.preferencesController.updateRpc({ rpcUrl, chainId, ticker, nickname, rpcPrefs })
this.networkController.setRpcTarget(rpcUrl, chainId, ticker, nickname, rpcPrefs)
@ -1884,31 +1883,31 @@ export default class MetamaskController extends EventEmitter {
/**
* 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} rpcUrl - A URL for a valid Ethereum RPC API.
* @param {string} 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.
*/
async setCustomRpc (rpcTarget, chainId, ticker = 'ETH', nickname = '', rpcPrefs = {}) {
async setCustomRpc (rpcUrl, chainId, ticker = 'ETH', nickname = '', rpcPrefs = {}) {
const frequentRpcListDetail = this.preferencesController.getFrequentRpcListDetail()
const rpcSettings = frequentRpcListDetail.find((rpc) => rpcTarget === rpc.rpcUrl)
const rpcSettings = frequentRpcListDetail.find((rpc) => rpcUrl === rpc.rpcUrl)
if (rpcSettings) {
this.networkController.setRpcTarget(rpcSettings.rpcUrl, rpcSettings.chainId, rpcSettings.ticker, rpcSettings.nickname, rpcPrefs)
} else {
this.networkController.setRpcTarget(rpcTarget, chainId, ticker, nickname, rpcPrefs)
await this.preferencesController.addToFrequentRpcList(rpcTarget, chainId, ticker, nickname, rpcPrefs)
this.networkController.setRpcTarget(rpcUrl, chainId, ticker, nickname, rpcPrefs)
await this.preferencesController.addToFrequentRpcList(rpcUrl, chainId, ticker, nickname, rpcPrefs)
}
return rpcTarget
return rpcUrl
}
/**
* A method for deleting a selected custom URL.
* @param {string} rpcTarget - A RPC URL to delete.
* @param {string} rpcUrl - A RPC URL to delete.
*/
async delCustomRpc (rpcTarget) {
await this.preferencesController.removeFromFrequentRpcList(rpcTarget)
async delCustomRpc (rpcUrl) {
await this.preferencesController.removeFromFrequentRpcList(rpcUrl)
}
async initializeThreeBox () {

@ -0,0 +1,38 @@
import { cloneDeep } from 'lodash'
const version = 48
/**
* 1. Delete NetworkController.settings
* 2a. Delete NetworkController.provider if set to type 'rpc'.
* It will be re-set to Mainnet on background initialization.
* 2b. Re-key provider.rpcTarget to provider.rpcUrl
*/
export default {
version,
async migrate (originalVersionedData) {
const versionedData = cloneDeep(originalVersionedData)
versionedData.meta.version = version
const state = versionedData.data
versionedData.data = transformState(state)
return versionedData
},
}
function transformState (state = {}) {
// 1. Delete NetworkController.settings
delete state.NetworkController?.settings
// 2. Delete NetworkController.provider or rename rpcTarget key
if (state.NetworkController?.provider?.type === 'rpc') {
delete state.NetworkController.provider
} else if (state.NetworkController?.provider) {
if ('rpcTarget' in state.NetworkController.provider) {
const rpcUrl = state.NetworkController.provider.rpcTarget
state.NetworkController.provider.rpcUrl = rpcUrl
}
delete state.NetworkController?.provider?.rpcTarget
}
return state
}

@ -58,6 +58,7 @@ const migrations = [
require('./045').default,
require('./046').default,
require('./047').default,
require('./048').default,
]
export default migrations

@ -30,12 +30,10 @@
"network": "5777",
"provider": {
"nickname": "",
"rpcTarget": "",
"rpcUrl": "",
"chainId": "0x1691",
"ticker": "ETH",
"type": "localhost"
},
"settings": {
"ticker": "ETH"
}
},
"OnboardingController": {

@ -30,12 +30,10 @@
"network": "5777",
"provider": {
"nickname": "",
"rpcTarget": "",
"rpcUrl": "",
"chainId": "0x1691",
"ticker": "ETH",
"type": "localhost"
},
"settings": {
"ticker": "ETH"
}
},
"OnboardingController": {

@ -33,14 +33,12 @@
"NetworkController": {
"provider": {
"nickname": "",
"rpcTarget": "",
"rpcUrl": "",
"chainId": "0x1691",
"ticker": "ETH",
"type": "localhost"
},
"network": "5777",
"settings": {
"ticker": "ETH"
}
"network": "5777"
},
"OnboardingController": {
"onboardingTabs": {},

@ -1252,15 +1252,15 @@ describe('MetaMask', function () {
})
describe('Stores custom RPC history', function () {
const customRpcUrls = [
'http://127.0.0.1:8545/1',
'http://127.0.0.1:8545/2',
'http://127.0.0.1:8545/3',
'http://127.0.0.1:8545/4',
const customRpcInfo = [
{ rpcUrl: 'http://127.0.0.1:8545/1', chainId: '0x1' },
{ rpcUrl: 'http://127.0.0.1:8545/2', chainId: '0x2' },
{ rpcUrl: 'http://127.0.0.1:8545/3', chainId: '0x3' },
{ rpcUrl: 'http://127.0.0.1:8545/4', chainId: '0x4' },
]
customRpcUrls.forEach((customRpcUrl) => {
it(`creates custom RPC: ${customRpcUrl}`, async function () {
customRpcInfo.forEach(({ rpcUrl, chainId }) => {
it(`creates custom RPC: '${rpcUrl}' with chainId '${chainId}'`, async function () {
await driver.clickElement(By.css('.network-name'))
await driver.delay(regularDelayMs)
@ -1270,9 +1270,14 @@ describe('MetaMask', function () {
await driver.findElement(By.css('.settings-page__sub-header-text'))
const customRpcInputs = await driver.findElements(By.css('input[type="text"]'))
const customRpcInput = customRpcInputs[1]
await customRpcInput.clear()
await customRpcInput.sendKeys(customRpcUrl)
const rpcUrlInput = customRpcInputs[1]
const chainIdInput = customRpcInputs[2]
await rpcUrlInput.clear()
await rpcUrlInput.sendKeys(rpcUrl)
await chainIdInput.clear()
await chainIdInput.sendKeys(chainId)
await driver.clickElement(By.css('.network-form__footer .btn-secondary'))
await driver.delay(largeDelayMs * 2)
@ -1294,7 +1299,7 @@ describe('MetaMask', function () {
// only recent 3 are found and in correct order (most recent at the top)
const customRpcs = await driver.findElements(By.xpath(`//span[contains(text(), 'http://127.0.0.1:8545/')]`))
assert.equal(customRpcs.length, customRpcUrls.length)
assert.equal(customRpcs.length, customRpcInfo.length)
})
it('deletes a custom RPC', async function () {

@ -6,7 +6,7 @@ import * as actionConstants from '../../../ui/app/store/actionConstants'
describe('config view actions', function () {
const initialState = {
metamask: {
rpcTarget: 'foo',
rpcUrl: 'foo',
frequentRpcList: [],
},
appState: {
@ -18,7 +18,7 @@ describe('config view actions', function () {
freeze(initialState)
describe('SET_RPC_TARGET', function () {
it('sets the state.metamask.rpcTarget property of the state to the action.value', function () {
it('sets the state.metamask.rpcUrl property of the state to the action.value', function () {
const action = {
type: actionConstants.SET_RPC_TARGET,
value: 'foo',
@ -26,7 +26,7 @@ describe('config view actions', function () {
const result = reducers(initialState, action)
assert.equal(result.metamask.provider.type, 'rpc')
assert.equal(result.metamask.provider.rpcTarget, 'foo')
assert.equal(result.metamask.provider.rpcUrl, 'foo')
})
})
})

@ -524,19 +524,19 @@ describe('MetaMaskController', function () {
})
describe('#setCustomRpc', function () {
let rpcTarget
let rpcUrl
beforeEach(function () {
rpcTarget = metamaskController.setCustomRpc(CUSTOM_RPC_URL)
rpcUrl = metamaskController.setCustomRpc(CUSTOM_RPC_URL)
})
it('returns custom RPC that when called', async function () {
assert.equal(await rpcTarget, CUSTOM_RPC_URL)
assert.equal(await rpcUrl, CUSTOM_RPC_URL)
})
it('changes the network controller rpc', function () {
const networkControllerState = metamaskController.networkController.store.getState()
assert.equal(networkControllerState.provider.rpcTarget, CUSTOM_RPC_URL)
assert.equal(networkControllerState.provider.rpcUrl, CUSTOM_RPC_URL)
})
})

@ -1,4 +1,5 @@
import assert from 'assert'
import { strict as assert } from 'assert'
import sinon from 'sinon'
import NetworkController from '../../../../../app/scripts/controllers/network'
import { getNetworkDisplayName } from '../../../../../app/scripts/controllers/network/util'
@ -34,9 +35,9 @@ describe('NetworkController', function () {
describe('#setNetworkState', function () {
it('should update the network', function () {
networkController.setNetworkState(1, 'rpc')
networkController.setNetworkState('1')
const networkState = networkController.getNetworkState()
assert.equal(networkState, 1, 'network is 1')
assert.equal(networkState, '1', 'network is 1')
})
})
@ -47,11 +48,21 @@ describe('NetworkController', function () {
const { type } = networkController.getProviderConfig()
assert.equal(type, 'mainnet', 'provider type is updated')
})
it('should set the network to loading', function () {
networkController.initializeProvider(networkControllerProviderConfig)
const spy = sinon.spy(networkController, 'setNetworkState')
networkController.setProviderType('mainnet')
const loading = networkController.isNetworkLoading()
assert.ok(loading, 'network is loading')
assert.equal(
spy.callCount, 1,
'should have called setNetworkState 2 times',
)
assert.ok(
spy.calledOnceWithExactly('loading'),
'should have called with "loading" first',
)
})
})
})

@ -498,28 +498,37 @@ describe('preferences controller', function () {
describe('#updateRpc', function () {
it('should update the rpcDetails properly', function () {
preferencesController.store.updateState({ frequentRpcListDetail: [{}, { rpcUrl: 'test' }, {}] })
preferencesController.updateRpc({ rpcUrl: 'test', chainId: '1' })
preferencesController.updateRpc({ rpcUrl: 'test/1', chainId: '1' })
preferencesController.updateRpc({ rpcUrl: 'test/2', chainId: '1' })
preferencesController.updateRpc({ rpcUrl: 'test/3', chainId: '1' })
preferencesController.store.updateState({ frequentRpcListDetail: [{}, { rpcUrl: 'test', chainId: '0x1' }, {}] })
preferencesController.updateRpc({ rpcUrl: 'test', chainId: '0x1' })
preferencesController.updateRpc({ rpcUrl: 'test/1', chainId: '0x1' })
preferencesController.updateRpc({ rpcUrl: 'test/2', chainId: '0x1' })
preferencesController.updateRpc({ rpcUrl: 'test/3', chainId: '0x1' })
const list = preferencesController.getFrequentRpcListDetail()
assert.deepEqual(list[1], { rpcUrl: 'test', chainId: '1' })
assert.deepEqual(list[1], { rpcUrl: 'test', chainId: '0x1' })
})
})
describe('on updateFrequentRpcList', function () {
describe('adding and removing from frequentRpcListDetail', 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: '', rpcPrefs: {} }])
preferencesController.addToFrequentRpcList('rpc_url', '1')
assert.deepEqual(preferencesController.store.getState().frequentRpcListDetail, [{ rpcUrl: 'rpc_url', chainId: '1', ticker: 'ETH', nickname: '', rpcPrefs: {} }])
preferencesController.addToFrequentRpcList('rpc_url', '0x1')
preferencesController.addToFrequentRpcList('http://localhost:8545', '0x1')
assert.deepEqual(preferencesController.store.getState().frequentRpcListDetail, [{ rpcUrl: 'rpc_url', chainId: '0x1', ticker: 'ETH', nickname: '', rpcPrefs: {} }])
preferencesController.addToFrequentRpcList('rpc_url', '0x1')
assert.deepEqual(preferencesController.store.getState().frequentRpcListDetail, [{ rpcUrl: 'rpc_url', chainId: '0x1', ticker: 'ETH', nickname: '', rpcPrefs: {} }])
})
it('should throw if chainId is invalid', function () {
assert.throws(
() => {
preferencesController.addToFrequentRpcList('rpc_url', '1')
},
'should throw on invalid chainId',
)
})
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: '', rpcPrefs: {} }])
preferencesController.addToFrequentRpcList('rpc_url', '0x1')
assert.deepEqual(preferencesController.store.getState().frequentRpcListDetail, [{ rpcUrl: 'rpc_url', chainId: '0x1', ticker: 'ETH', nickname: '', rpcPrefs: {} }])
preferencesController.removeFromFrequentRpcList('other_rpc_url')
preferencesController.removeFromFrequentRpcList('http://localhost:8545')
preferencesController.removeFromFrequentRpcList('rpc_url')

@ -1,5 +1,9 @@
import assert from 'assert'
import { getEnvironmentType, sufficientBalance } from '../../../app/scripts/lib/util'
import { strict as assert } from 'assert'
import {
getEnvironmentType,
sufficientBalance,
isPrefixedFormattedHexString,
} from '../../../app/scripts/lib/util'
import {
ENVIRONMENT_TYPE_POPUP,
@ -88,4 +92,75 @@ describe('app utils', function () {
assert.ok(!result, 'insufficient balance found.')
})
})
describe('isPrefixedFormattedHexString', function () {
it('should return true for valid hex strings', function () {
assert.equal(
isPrefixedFormattedHexString('0x1'), true,
'should return true',
)
assert.equal(
isPrefixedFormattedHexString('0xa'), true,
'should return true',
)
assert.equal(
isPrefixedFormattedHexString('0xabcd1123fae909aad87452'), true,
'should return true',
)
})
it('should return false for invalid hex strings', function () {
assert.equal(
isPrefixedFormattedHexString('0x'), false,
'should return false',
)
assert.equal(
isPrefixedFormattedHexString('0x0'), false,
'should return false',
)
assert.equal(
isPrefixedFormattedHexString('0x01'), false,
'should return false',
)
assert.equal(
isPrefixedFormattedHexString(' 0x1'), false,
'should return false',
)
assert.equal(
isPrefixedFormattedHexString('0x1 '), false,
'should return false',
)
assert.equal(
isPrefixedFormattedHexString('0x1afz'), false,
'should return false',
)
assert.equal(
isPrefixedFormattedHexString('z'), false,
'should return false',
)
assert.equal(
isPrefixedFormattedHexString(2), false,
'should return false',
)
assert.equal(
isPrefixedFormattedHexString(['0x1']), false,
'should return false',
)
assert.equal(
isPrefixedFormattedHexString(), false,
'should return false',
)
})
})
})

@ -13,7 +13,7 @@ const initialState = {
NetworkController: {
provider: {
type: 'rpc',
rpcTarget: 'http://localhost:8545',
rpcUrl: 'http://localhost:8545',
},
},
}

@ -0,0 +1,126 @@
import { strict as assert } from 'assert'
import migration48 from '../../../app/scripts/migrations/048'
describe('migration #48', function () {
it('should update the version metadata', async function () {
const oldStorage = {
'meta': {
'version': 47,
},
'data': {},
}
const newStorage = await migration48.migrate(oldStorage)
assert.deepEqual(newStorage.meta, {
'version': 48,
})
})
it('should delete NetworkController.settings', async function () {
const oldStorage = {
meta: {},
data: {
NetworkController: {
settings: {
fizz: 'buzz',
},
provider: {
type: 'notRpc',
},
},
foo: 'bar',
},
}
const newStorage = await migration48.migrate(oldStorage)
assert.deepEqual(newStorage.data, {
NetworkController: {
provider: {
type: 'notRpc',
},
},
foo: 'bar',
})
})
it('should delete NetworkController.provider if the type is "rpc"', async function () {
const oldStorage = {
meta: {},
data: {
NetworkController: {
provider: {
type: 'rpc',
fizz: 'buzz',
},
foo: 'bar',
},
foo: 'bar',
},
}
const newStorage = await migration48.migrate(oldStorage)
assert.deepEqual(newStorage.data, {
NetworkController: {
foo: 'bar',
},
foo: 'bar',
})
})
it('should re-key NetworkController.provider.rpcTarget to rpcUrl if the type is not "rpc"', async function () {
const oldStorage = {
meta: {},
data: {
NetworkController: {
provider: {
type: 'someType',
rpcTarget: 'foo.xyz',
fizz: 'buzz',
},
foo: 'bar',
},
foo: 'bar',
},
}
const newStorage = await migration48.migrate(oldStorage)
assert.deepEqual(newStorage.data, {
NetworkController: {
foo: 'bar',
provider: {
type: 'someType',
rpcUrl: 'foo.xyz',
fizz: 'buzz',
},
},
foo: 'bar',
})
})
it('should do nothing if affected state does not exist', async function () {
const oldStorage = {
meta: {},
data: {
NetworkController: {
provider: {
type: 'notRpc',
},
},
foo: 'bar',
},
}
const newStorage = await migration48.migrate(oldStorage)
assert.deepEqual(oldStorage.data, newStorage.data)
})
it('should do nothing if state is empty', async function () {
const oldStorage = {
meta: {},
data: {},
}
const newStorage = await migration48.migrate(oldStorage)
assert.deepEqual(oldStorage.data, newStorage.data)
})
})

@ -27,7 +27,7 @@ describe('MetaMask Reducers', function () {
value: 'https://custom.rpc',
})
assert.equal(state.provider.rpcTarget, 'https://custom.rpc')
assert.equal(state.provider.rpcUrl, 'https://custom.rpc')
})
it('sets provider type', function () {

@ -1,12 +1,21 @@
import React from 'react'
import { useSelector } from 'react-redux'
import PropTypes from 'prop-types'
import { alertIsOpen as unconnectedAccountAlertIsOpen } from '../../../ducks/alerts/unconnected-account'
import { alertIsOpen as invalidCustomNetworkAlertIsOpen } from '../../../ducks/alerts/invalid-custom-network'
import InvalidCustomNetworkAlert from './invalid-custom-network-alert'
import UnconnectedAccountAlert from './unconnected-account-alert'
const Alerts = () => {
const Alerts = ({ history }) => {
const _invalidCustomNetworkAlertIsOpen = useSelector(invalidCustomNetworkAlertIsOpen)
const _unconnectedAccountAlertIsOpen = useSelector(unconnectedAccountAlertIsOpen)
if (_invalidCustomNetworkAlertIsOpen) {
return (
<InvalidCustomNetworkAlert history={history} />
)
}
if (_unconnectedAccountAlertIsOpen) {
return (
<UnconnectedAccountAlert />
@ -16,4 +25,8 @@ const Alerts = () => {
return null
}
Alerts.propTypes = {
history: PropTypes.object.isRequired,
}
export default Alerts

@ -1 +1,2 @@
@import './invalid-custom-network-alert/invalid-custom-network-alert';
@import './unconnected-account-alert/unconnected-account-alert';

@ -0,0 +1 @@
export { default } from './invalid-custom-network-alert'

@ -0,0 +1,97 @@
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
import PropTypes from 'prop-types'
import { ALERT_STATE } from '../../../../ducks/alerts'
import {
dismissAlert,
getAlertState,
getNetworkName,
} from '../../../../ducks/alerts/invalid-custom-network'
import Popover from '../../../ui/popover'
import Button from '../../../ui/button'
import { useI18nContext } from '../../../../hooks/useI18nContext'
import { NETWORKS_ROUTE } from '../../../../helpers/constants/routes'
const {
ERROR,
LOADING,
} = ALERT_STATE
const InvalidCustomNetworkAlert = ({ history }) => {
const t = useI18nContext()
const dispatch = useDispatch()
const alertState = useSelector(getAlertState)
const networkName = useSelector(getNetworkName)
const onClose = () => dispatch(dismissAlert())
const footer = (
<>
{
alertState === ERROR
? (
<div className="invalid-custom-network-alert__error">
{ t('failureMessage') }
</div>
)
: null
}
<div className="invalid-custom-network-alert__footer-row">
<Button
disabled={alertState === LOADING}
onClick={onClose}
type="secondary"
className="invalid-custom-network-alert__footer-row-button"
>
{ t('dismiss') }
</Button>
<Button
disabled={alertState === LOADING}
onClick={async () => {
await onClose()
history.push(NETWORKS_ROUTE)
}}
type="primary"
className="invalid-custom-network-alert__footer-row-button"
>
{ t('settings') }
</Button>
</div>
</>
)
return (
<Popover
title={t('invalidCustomNetworkAlertTitle')}
onClose={onClose}
contentClassName="invalid-custom-network-alert__content"
footerClassName="invalid-custom-network-alert__footer"
footer={footer}
>
<p>{t('invalidCustomNetworkAlertContent1', [networkName])}</p>
<p>{t('invalidCustomNetworkAlertContent2')}</p>
<p>
{
t('invalidCustomNetworkAlertContent3', [(
<span
key="invalidCustomNetworkAlertContentLink"
className="invalid-custom-network-alert__content-link"
onClick={
() => global.platform.openTab({ url: 'https://chainid.network' })
}
>
chainId.network
</span>
)])
}
</p>
</Popover>
)
}
InvalidCustomNetworkAlert.propTypes = {
history: PropTypes.object.isRequired,
}
export default InvalidCustomNetworkAlert

@ -0,0 +1,57 @@
.invalid-custom-network-alert {
&__content {
border-radius: 0;
padding: 0 24px 16px 24px;
> p {
@include Paragraph;
font-size: 14px;
padding-bottom: 12px;
}
> p:last-of-type {
padding-bottom: 0;
}
}
&__content-link {
color: $primary-blue;
cursor: pointer;
}
&__footer {
flex-direction: column;
> :only-child {
margin: 0;
width: 100%;
}
}
&__footer-row {
display: flex;
flex-direction: row;
justify-content: space-between;
& &-button {
height: 40px;
width: 50%;
border-radius: 100px;
margin-right: 24px;
&:last-of-type {
margin-right: 0;
}
}
}
&__error {
margin-bottom: 16px;
padding: 16px;
font-size: 14px;
border: 1px solid $accent-red;
background: #f8eae8;
border-radius: 3px;
}
}

@ -1,8 +1,8 @@
import React, { useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { ALERT_STATE } from '../../../../ducks/alerts'
import {
ALERT_STATE,
connectAccount,
dismissAlert,
dismissAndDisableAlert,

@ -4,7 +4,11 @@ import { connect } from 'react-redux'
import { withRouter } from 'react-router-dom'
import { compose } from 'redux'
import * as actions from '../../../store/actions'
import {
openAlert as displayInvalidCustomNetworkAlert,
} from '../../../ducks/alerts/invalid-custom-network'
import { NETWORKS_ROUTE } from '../../../helpers/constants/routes'
import { isPrefixedFormattedHexString } from '../../../../../app/scripts/lib/util'
import { Dropdown, DropdownMenuItem } from './components/dropdown'
import NetworkDropdownIcon from './components/network-dropdown-icon'
@ -22,7 +26,6 @@ function mapStateToProps (state) {
provider: state.metamask.provider,
frequentRpcListDetail: state.metamask.frequentRpcListDetail || [],
networkDropdownOpen: state.appState.networkDropdownOpen,
network: state.metamask.network,
}
}
@ -38,7 +41,12 @@ function mapDispatchToProps (dispatch) {
dispatch(actions.delRpcTarget(target))
},
hideNetworkDropdown: () => dispatch(actions.hideNetworkDropdown()),
setNetworksTabAddMode: (isInAddMode) => dispatch(actions.setNetworksTabAddMode(isInAddMode)),
setNetworksTabAddMode: (isInAddMode) => {
dispatch(actions.setNetworksTabAddMode(isInAddMode))
},
displayInvalidCustomNetworkAlert: (networkName) => {
dispatch(displayInvalidCustomNetworkAlert(networkName))
},
}
}
@ -51,12 +59,11 @@ class NetworkDropdown extends Component {
static propTypes = {
provider: PropTypes.shape({
nickname: PropTypes.string,
rpcTarget: PropTypes.string,
rpcUrl: PropTypes.string,
type: PropTypes.string,
ticker: PropTypes.string,
}).isRequired,
setProviderType: PropTypes.func.isRequired,
network: PropTypes.string.isRequired,
setRpcTarget: PropTypes.func.isRequired,
hideNetworkDropdown: PropTypes.func.isRequired,
setNetworksTabAddMode: PropTypes.func.isRequired,
@ -64,6 +71,7 @@ class NetworkDropdown extends Component {
networkDropdownOpen: PropTypes.bool.isRequired,
history: PropTypes.object.isRequired,
delRpcTarget: PropTypes.func.isRequired,
displayInvalidCustomNetworkAlert: PropTypes.func.isRequired,
}
handleClick (newProviderType) {
@ -84,64 +92,30 @@ class NetworkDropdown extends Component {
setProviderType(newProviderType)
}
renderCustomOption (provider) {
const { rpcTarget, type, ticker, nickname } = provider
const { network } = this.props
if (type !== 'rpc') {
return null
}
switch (rpcTarget) {
case 'http://localhost:8545':
return null
default:
return (
<DropdownMenuItem
key={rpcTarget}
onClick={() => this.props.setRpcTarget(rpcTarget, network, ticker, nickname)}
closeMenu={() => this.props.hideNetworkDropdown()}
style={{
fontSize: '16px',
lineHeight: '20px',
padding: '12px 0',
}}
>
<i className="fa fa-check" />
<i className="fa fa-question-circle fa-med menu-icon-circle" />
<span
className="network-name-item"
style={{
color: '#ffffff',
}}
>
{nickname || rpcTarget}
</span>
</DropdownMenuItem>
)
}
}
renderCommonRpc (rpcListDetail, provider) {
renderCustomRpcList (rpcListDetail, provider) {
const reversedRpcListDetail = rpcListDetail.slice().reverse()
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 { rpcUrl, chainId, ticker = 'ETH', nickname = '' } = entry
const currentRpcTarget = (
provider.type === 'rpc' && rpcUrl === provider.rpcUrl
)
if ((rpc === 'http://localhost:8545') || currentRpcTarget) {
if (rpcUrl === 'http://localhost:8545') {
return null
}
const { chainId } = entry
return (
<DropdownMenuItem
key={`common${rpc}`}
key={`common${rpcUrl}`}
closeMenu={() => this.props.hideNetworkDropdown()}
onClick={() => this.props.setRpcTarget(rpc, chainId, ticker, nickname)}
onClick={() => {
if (isPrefixedFormattedHexString(chainId)) {
this.props.setRpcTarget(rpcUrl, chainId, ticker, nickname)
} else {
this.props.displayInvalidCustomNetworkAlert(nickname || rpcUrl)
}
}}
style={{
fontSize: '16px',
lineHeight: '20px',
@ -162,15 +136,21 @@ class NetworkDropdown extends Component {
: '#9b9b9b',
}}
>
{nickname || rpc}
{nickname || rpcUrl}
</span>
<i
className="fa fa-times delete"
onClick={(e) => {
e.stopPropagation()
this.props.delRpcTarget(rpc)
}}
/>
{
currentRpcTarget
? null
: (
<i
className="fa fa-times delete"
onClick={(e) => {
e.stopPropagation()
this.props.delRpcTarget(rpcUrl)
}}
/>
)
}
</DropdownMenuItem>
)
})
@ -202,7 +182,7 @@ class NetworkDropdown extends Component {
}
render () {
const { provider: { type: providerType, rpcTarget: activeNetwork }, setNetworksTabAddMode } = this.props
const { provider: { type: providerType, rpcUrl: activeNetwork }, setNetworksTabAddMode } = this.props
const rpcListDetail = this.props.frequentRpcListDetail
const isOpen = this.props.networkDropdownOpen
const dropdownMenuItemStyle = {
@ -382,8 +362,7 @@ class NetworkDropdown extends Component {
{this.context.t('localhost')}
</span>
</DropdownMenuItem>
{this.renderCustomOption(this.props.provider)}
{this.renderCommonRpc(rpcListDetail, this.props.provider)}
{this.renderCustomRpcList(rpcListDetail, this.props.provider)}
<DropdownMenuItem
closeMenu={() => this.props.hideNetworkDropdown()}
onClick={() => {

@ -50,7 +50,8 @@ describe('Network Dropdown', function () {
'type': 'test',
},
frequentRpcListDetail: [
{ rpcUrl: 'http://localhost:7545' },
{ chainId: '0x1a', rpcUrl: 'http://localhost:7545' },
{ rpcUrl: 'http://localhost:7546' },
],
},
appState: {
@ -65,8 +66,8 @@ describe('Network Dropdown', function () {
)
})
it('renders 7 DropDownMenuItems ', function () {
assert.equal(wrapper.find(DropdownMenuItem).length, 8)
it('renders 9 DropDownMenuItems ', function () {
assert.equal(wrapper.find(DropdownMenuItem).length, 9)
})
it('checks background color for first NetworkDropdownIcon', function () {
@ -93,8 +94,8 @@ describe('Network Dropdown', function () {
assert.equal(wrapper.find(NetworkDropdownIcon).at(5).prop('innerBorder'), '1px solid #9b9b9b')
})
it('checks dropdown for frequestRPCList from state ', function () {
assert.equal(wrapper.find(DropdownMenuItem).at(6).text(), '✓http://localhost:7545')
it('checks dropdown for frequestRPCList from state', function () {
assert.equal(wrapper.find(DropdownMenuItem).at(6).text(), '✓http://localhost:7546')
})
it('checks background color for seventh NetworkDropdownIcon', function () {

@ -12,10 +12,10 @@ const mapStateToProps = (state) => {
provider,
network,
} = state.metamask
const { rpcTarget, chainId, ticker, nickname, type } = provider
const { rpcUrl, chainId, ticker, nickname, type } = provider
const setProviderArgs = type === 'rpc'
? [rpcTarget, chainId, ticker, nickname]
? [rpcUrl, chainId, ticker, nickname]
: [provider.type]
return {

@ -47,7 +47,7 @@ export default class Network extends Component {
provider: PropTypes.shape({
type: PropTypes.string,
nickname: PropTypes.string,
rpcTarget: PropTypes.string,
rpcUrl: PropTypes.string,
}).isRequired,
disabled: PropTypes.bool,
onClick: PropTypes.func.isRequired,
@ -69,7 +69,7 @@ export default class Network extends Component {
if (provider) {
providerName = provider.type
providerNick = provider.nickname || ''
providerUrl = provider.rpcTarget
providerUrl = provider.rpcUrl
}
switch (providerName) {

@ -22,7 +22,7 @@
&-header {
display: flex;
padding: 24px;
padding: 24px 24px 16px;
flex-direction: column;
background: white;
position: relative;
@ -37,7 +37,6 @@
font-weight: bold;
line-height: 25px;
padding-bottom: 8px;
h2 {
white-space: nowrap;
@ -53,6 +52,7 @@
&__subtitle {
@include H6;
padding-top: 8px;
line-height: 20px;
}

@ -0,0 +1,6 @@
export const ALERT_STATE = {
CLOSED: 'CLOSED',
ERROR: 'ERROR',
LOADING: 'LOADING',
OPEN: 'OPEN',
}

@ -1 +1,4 @@
export { default as unconnectedAccount } from './unconnected-account'
export { default as invalidCustomNetwork } from './invalid-custom-network'
export { ALERT_STATE } from './enums'

@ -0,0 +1,51 @@
import { createSlice } from '@reduxjs/toolkit'
import { ALERT_TYPES } from '../../../../app/scripts/controllers/alert'
import { ALERT_STATE } from './enums'
// Constants
const name = ALERT_TYPES.invalidCustomNetwork
const initialState = {
state: ALERT_STATE.CLOSED,
networkName: '',
}
// Slice (reducer plus auto-generated actions and action creators)
const slice = createSlice({
name,
initialState,
reducers: {
openAlert: (state, action) => {
state.state = ALERT_STATE.OPEN
state.networkName = action.payload
},
dismissAlert: (state) => {
state.state = ALERT_STATE.CLOSED
state.networkName = ''
},
},
})
const { actions, reducer } = slice
export default reducer
// Selectors
export const getAlertState = (state) => state[name].state
export const getNetworkName = (state) => state[name].networkName
export const alertIsOpen = (state) => state[name].state !== ALERT_STATE.CLOSED
// Actions / action-creators
const {
openAlert,
dismissAlert,
} = actions
export { openAlert, dismissAlert }

@ -12,16 +12,10 @@ import {
getOriginOfCurrentTab,
getSelectedAddress,
} from '../../selectors'
import { ALERT_STATE } from './enums'
// Constants
export const ALERT_STATE = {
CLOSED: 'CLOSED',
ERROR: 'ERROR',
LOADING: 'LOADING',
OPEN: 'OPEN',
}
const name = ALERT_TYPES.unconnectedAccount
const initialState = {

@ -6,10 +6,11 @@ import sendReducer from './send/send.duck'
import appStateReducer from './app/app'
import confirmTransactionReducer from './confirm-transaction/confirm-transaction.duck'
import gasReducer from './gas/gas.duck'
import { unconnectedAccount } from './alerts'
import { invalidCustomNetwork, unconnectedAccount } from './alerts'
import historyReducer from './history/history'
export default combineReducers({
[ALERT_TYPES.invalidCustomNetwork]: invalidCustomNetwork,
[ALERT_TYPES.unconnectedAccount]: unconnectedAccount,
activeTab: (s) => (s === undefined ? null : s),
metamask: metamaskReducer,

@ -6,7 +6,7 @@ export default function reduceMetamask (state = {}, action) {
isInitialized: false,
isUnlocked: false,
isAccountMenuOpen: false,
rpcTarget: 'https://rawtestrpc.metamask.io/',
rpcUrl: 'https://rawtestrpc.metamask.io/',
identities: {},
unapprovedTxs: {},
frequentRpcList: [],
@ -65,7 +65,7 @@ export default function reduceMetamask (state = {}, action) {
...metamaskState,
provider: {
type: 'rpc',
rpcTarget: action.value,
rpcUrl: action.value,
},
}
@ -375,6 +375,8 @@ export const getCurrentLocale = (state) => state.metamask.currentLocale
export const getAlertEnabledness = (state) => state.metamask.alertEnabledness
export const getInvalidCustomNetworkAlertEnabledness = (state) => getAlertEnabledness(state)[ALERT_TYPES.invalidCustomNetwork]
export const getUnconnectedAccountAlertEnabledness = (state) => getAlertEnabledness(state)[ALERT_TYPES.unconnectedAccount]
export const getUnconnectedAccountAlertShown = (state) => state.metamask.unconnectedAccountAlertShownOrigins

@ -256,7 +256,7 @@ export default class Routes extends Component {
{
isUnlocked
? (
<Alerts />
<Alerts history={this.props.history} />
)
: null
}

@ -96,6 +96,12 @@
}
&__network-form-label {
display: flex;
align-items: center;
}
&__network-form-label-text {
font-family: Roboto;
font-style: normal;
font-weight: normal;
font-size: 14px;
@ -103,6 +109,11 @@
color: #000;
}
&__network-form-label-tooltip {
margin-left: 5px;
font-size: 12px;
}
&__networks-list {
flex: 0.5 0 auto;
max-width: 343px;

@ -1,8 +1,11 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import validUrl from 'valid-url'
import BigNumber from 'bignumber.js'
import TextField from '../../../../components/ui/text-field'
import Button from '../../../../components/ui/button'
import Tooltip from '../../../../components/ui/tooltip'
import { isPrefixedFormattedHexString } from '../../../../../../app/scripts/lib/util'
export default class NetworkForm extends PureComponent {
static contextTypes = {
@ -97,10 +100,17 @@ export default class NetworkForm extends PureComponent {
const {
networkName,
rpcUrl,
chainId,
chainId: stateChainId,
ticker,
blockExplorerUrl,
} = this.state
// Ensure chainId is a 0x-prefixed, lowercase hex string
let chainId = stateChainId.trim().toLowerCase()
if (!chainId.startsWith('0x')) {
chainId = `0x${(new BigNumber(chainId, 10)).toString(16)}`
}
if (propsRpcUrl && rpcUrl !== propsRpcUrl) {
editRpc(propsRpcUrl, rpcUrl, chainId, ticker, networkName, {
blockExplorerUrl: blockExplorerUrl || rpcPrefs.blockExplorerUrl,
@ -168,13 +178,30 @@ export default class NetworkForm extends PureComponent {
)
}
renderFormTextField (fieldKey, textFieldId, onChange, value, optionalTextFieldKey) {
renderFormTextField (fieldKey, textFieldId, onChange, value, optionalTextFieldKey, tooltipText) {
const { errors } = this.state
const { viewOnly } = this.props
return (
<div className="networks-tab__network-form-row">
<div className="networks-tab__network-form-label">{this.context.t(optionalTextFieldKey || fieldKey)}</div>
<div className="networks-tab__network-form-label">
<div className="networks-tab__network-form-label-text">
{this.context.t(optionalTextFieldKey || fieldKey)}
</div>
{
!viewOnly && tooltipText
? (
<Tooltip
position="top"
title={tooltipText}
wrapperClassName="networks-tab__network-form-label-tooltip"
>
<i className="fa fa-info-circle" />
</Tooltip>
)
: null
}
</div>
<TextField
type="text"
id={textFieldId}
@ -205,11 +232,23 @@ export default class NetworkForm extends PureComponent {
})
}
validateChainId = (chainId) => {
// eslint-disable-next-line radix
this.setErrorTo('chainId', Boolean(chainId) && Number.isNaN(parseInt(chainId))
? `${this.context.t('invalidInput')} chainId`
: '')
validateChainId = (chainIdArg = '') => {
const chainId = chainIdArg.trim()
let errorMessage = ''
if (chainId.startsWith('0x')) {
if (!(/^0x[0-9a-f]+$/ui).test(chainId)) {
errorMessage = this.context.t('invalidHexNumber')
} else if (!isPrefixedFormattedHexString(chainId)) {
errorMessage = this.context.t('invalidHexNumberLeadingZeros')
}
} else if (!(/^[0-9]+$/u).test(chainId)) {
errorMessage = this.context.t('invalidNumber')
} else if (chainId.startsWith('0')) {
errorMessage = this.context.t('invalidNumberLeadingZeros')
}
this.setErrorTo('chainId', errorMessage)
}
isValidWhenAppended = (url) => {
@ -262,7 +301,13 @@ export default class NetworkForm extends PureComponent {
errors,
} = this.state
const isSubmitDisabled = viewOnly || this.stateIsUnchanged() || Object.values(errors).some((x) => x) || !rpcUrl
const isSubmitDisabled = (
viewOnly ||
this.stateIsUnchanged() ||
!rpcUrl ||
!chainId ||
Object.values(errors).some((x) => x)
)
const deletable = !networksTabIsInAddMode && !isCurrentRpcTarget && !viewOnly
return (
@ -285,7 +330,8 @@ export default class NetworkForm extends PureComponent {
'chainId',
this.setStateWithValue('chainId', this.validateChainId),
chainId,
'optionalChainId',
null,
t('networkSettingsChainIdDescription'),
)}
{this.renderFormTextField(
'symbol',

@ -1,54 +1,68 @@
import {
GOERLI,
GOERLI_CHAIN_ID,
KOVAN,
KOVAN_CHAIN_ID,
LOCALHOST,
MAINNET,
MAINNET_CHAIN_ID,
RINKEBY,
RINKEBY_CHAIN_ID,
ROPSTEN,
ROPSTEN_CHAIN_ID,
} from '../../../../../app/scripts/controllers/network/enums'
const defaultNetworksData = [
{
labelKey: 'mainnet',
labelKey: MAINNET,
iconColor: '#29B6AF',
providerType: 'mainnet',
providerType: MAINNET,
rpcUrl: `https://mainnet.infura.io/v3/${process.env.INFURA_PROJECT_ID}`,
chainId: '1',
chainId: MAINNET_CHAIN_ID,
ticker: 'ETH',
blockExplorerUrl: 'https://etherscan.io',
},
{
labelKey: 'ropsten',
labelKey: ROPSTEN,
iconColor: '#FF4A8D',
providerType: 'ropsten',
providerType: ROPSTEN,
rpcUrl: `https://ropsten.infura.io/v3/${process.env.INFURA_PROJECT_ID}`,
chainId: '3',
chainId: ROPSTEN_CHAIN_ID,
ticker: 'ETH',
blockExplorerUrl: 'https://ropsten.etherscan.io',
},
{
labelKey: 'rinkeby',
labelKey: RINKEBY,
iconColor: '#F6C343',
providerType: 'rinkeby',
providerType: RINKEBY,
rpcUrl: `https://rinkeby.infura.io/v3/${process.env.INFURA_PROJECT_ID}`,
chainId: '4',
chainId: RINKEBY_CHAIN_ID,
ticker: 'ETH',
blockExplorerUrl: 'https://rinkeby.etherscan.io',
},
{
labelKey: 'goerli',
labelKey: GOERLI,
iconColor: '#3099f2',
providerType: 'goerli',
providerType: GOERLI,
rpcUrl: `https://goerli.infura.io/v3/${process.env.INFURA_PROJECT_ID}`,
chainId: '5',
chainId: GOERLI_CHAIN_ID,
ticker: 'ETH',
blockExplorerUrl: 'https://goerli.etherscan.io',
},
{
labelKey: 'kovan',
labelKey: KOVAN,
iconColor: '#9064FF',
providerType: 'kovan',
providerType: KOVAN,
rpcUrl: `https://kovan.infura.io/v3/${process.env.INFURA_PROJECT_ID}`,
chainId: '42',
chainId: KOVAN_CHAIN_ID,
ticker: 'ETH',
blockExplorerUrl: 'https://etherscan.io',
},
{
labelKey: 'localhost',
labelKey: LOCALHOST,
iconColor: 'white',
border: '1px solid #6A737D',
providerType: 'localhost',
providerType: LOCALHOST,
rpcUrl: 'http://localhost:8545/',
blockExplorerUrl: 'https://etherscan.io',
},

@ -43,7 +43,7 @@ const mapStateToProps = (state) => {
let networkDefaultedToProvider = false
if (!networkIsSelected && !networksTabIsInAddMode) {
selectedNetwork = networksToRender.find((network) => {
return network.rpcUrl === provider.rpcTarget || (network.providerType !== 'rpc' && network.providerType === provider.type)
return network.rpcUrl === provider.rpcUrl || (network.providerType !== 'rpc' && network.providerType === provider.type)
}) || {}
networkDefaultedToProvider = true
}
@ -54,7 +54,7 @@ const mapStateToProps = (state) => {
networkIsSelected,
networksTabIsInAddMode,
providerType: provider.type,
providerUrl: provider.rpcTarget,
providerUrl: provider.rpcUrl,
networkDefaultedToProvider,
}
}

@ -9,9 +9,9 @@ import {
import { getPermissionsRequestCount } from './permissions'
export function getNetworkIdentifier (state) {
const { metamask: { provider: { type, nickname, rpcTarget } } } = state
const { metamask: { provider: { type, nickname, rpcUrl } } } = state
return nickname || rpcTarget || type
return nickname || rpcUrl || type
}
export function getCurrentKeyring (state) {
@ -299,7 +299,7 @@ export const getBackgroundMetaMetricState = (state) => {
export function getRpcPrefsForCurrentProvider (state) {
const { frequentRpcListDetail, provider } = state.metamask
const selectRpcInfo = frequentRpcListDetail.find((rpcInfo) => rpcInfo.rpcUrl === provider.rpcTarget)
const selectRpcInfo = frequentRpcListDetail.find((rpcInfo) => rpcInfo.rpcUrl === provider.rpcUrl)
const { rpcPrefs = {} } = selectRpcInfo || {}
return rpcPrefs
}

@ -3,7 +3,7 @@ const state = {
'isInitialized': true,
'isUnlocked': true,
'featureFlags': { 'sendHexData': true },
'rpcTarget': 'https://rawtestrpc.metamask.io/',
'rpcUrl': 'https://rawtestrpc.metamask.io/',
'identities': {
'0xfdea65c8e26263f6d9a1b5de9555d2931a33b825': {
'address': '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825',

@ -14,7 +14,7 @@ import txHelper from './lib/tx-helper'
import { fetchLocale, loadRelativeTimeFormatLocaleData } from './app/helpers/utils/i18n-helper'
import switchDirection from './app/helpers/utils/switch-direction'
import { getPermittedAccountsForCurrentTab, getSelectedAddress } from './app/selectors'
import { ALERT_STATE } from './app/ducks/alerts/unconnected-account'
import { ALERT_STATE } from './app/ducks/alerts'
import {
getUnconnectedAccountAlertEnabledness,
getUnconnectedAccountAlertShown,
@ -88,7 +88,9 @@ async function startApp (metamaskState, backgroundConnection, opts) {
permittedAccountsForCurrentTab.length > 0 &&
!permittedAccountsForCurrentTab.includes(selectedAddress)
) {
draftInitialState[ALERT_TYPES.unconnectedAccount] = { state: ALERT_STATE.OPEN }
draftInitialState[ALERT_TYPES.unconnectedAccount] = {
state: ALERT_STATE.OPEN,
}
actions.setUnconnectedAccountAlertShown(origin)
}
}

Loading…
Cancel
Save