feature/default_network_editable
pldespaigne 6 years ago
commit 9a658ee53d
  1. 15
      .editorconfig
  2. 2
      .eslintrc
  3. 64
      CHANGELOG.md
  4. 61
      app/_locales/en/messages.json
  5. 234
      app/_locales/it/messages.json
  6. 2
      app/manifest.json
  7. 214
      app/scripts/contentscript.js
  8. 73
      app/scripts/controllers/app-state.js
  9. 2
      app/scripts/controllers/balance.js
  10. 136
      app/scripts/controllers/blacklist.js
  11. 19
      app/scripts/controllers/network/createBlockTracker.js
  12. 10
      app/scripts/controllers/network/createInfuraClient.js
  13. 6
      app/scripts/controllers/network/createJsonRpcClient.js
  14. 6
      app/scripts/controllers/network/createLocalhostClient.js
  15. 6
      app/scripts/controllers/network/enums.js
  16. 19
      app/scripts/controllers/network/network.js
  17. 5
      app/scripts/controllers/network/util.js
  18. 46
      app/scripts/controllers/preferences.js
  19. 124
      app/scripts/controllers/provider-approval.js
  20. 3
      app/scripts/controllers/recent-blocks.js
  21. 180
      app/scripts/controllers/shapeshift.js
  22. 18
      app/scripts/controllers/token-rates.js
  23. 59
      app/scripts/controllers/transactions/index.js
  24. 161
      app/scripts/controllers/transactions/nonce-tracker.js
  25. 53
      app/scripts/controllers/transactions/pending-tx-tracker.js
  26. 16
      app/scripts/controllers/transactions/tx-gas-utils.js
  27. 4
      app/scripts/controllers/transactions/tx-state-manager.js
  28. 17
      app/scripts/controllers/user-actions.js
  29. 29
      app/scripts/createStandardProvider.js
  30. 128
      app/scripts/inpage.js
  31. 26
      app/scripts/lib/backend-metametrics.js
  32. 4
      app/scripts/lib/buy-eth-url.js
  33. 16
      app/scripts/lib/createDnodeRemoteGetter.js
  34. 16
      app/scripts/lib/createProviderMiddleware.js
  35. 2
      app/scripts/lib/message-manager.js
  36. 2
      app/scripts/lib/personal-message-manager.js
  37. 184
      app/scripts/metamask-controller.js
  38. 2
      app/scripts/migrations/024.js
  39. 2
      app/scripts/migrations/025.js
  40. 14
      app/scripts/platforms/extension.js
  41. 6
      development/backGroundConnectionModifiers.js
  42. 16
      development/metamaskbot-build-announce.js
  43. 3
      development/states/conf-tx.json
  44. 4
      development/states/confirm-new-ui.json
  45. 4
      development/states/confirm-sig-requests.json
  46. 4
      development/states/currency-localization.json
  47. 5
      development/states/navigate-txs.json
  48. 5
      development/states/pending-tx.json
  49. 4
      development/states/send-edit.json
  50. 4
      development/states/send-new-ui.json
  51. 3
      development/states/send.json
  52. 4
      development/states/tx-list-items.json
  53. 71
      docs/creating-metrics-events.md
  54. 5
      docs/design-system.md
  55. 5
      docs/limited_site_access.md
  56. 4
      docs/porting_to_new_environment.md
  57. 9
      docs/publishing.md
  58. 6
      gentests.js
  59. 2
      gulpfile.js
  60. 450
      package-lock.json
  61. 17
      package.json
  62. 5
      test/data/v17-long-history.json
  63. 37
      test/e2e/beta/contract-test/contract.js
  64. 2
      test/e2e/beta/contract-test/index.html
  65. 55
      test/e2e/beta/from-import-beta-ui.spec.js
  66. 2
      test/e2e/beta/metamask-beta-responsive-ui.spec.js
  67. 167
      test/e2e/beta/metamask-beta-ui.spec.js
  68. 9
      test/e2e/beta/run-web3.sh
  69. 365
      test/e2e/beta/web3.spec.js
  70. 21
      test/integration/lib/confirm-sig-requests.js
  71. 13
      test/integration/lib/currency-localization.js
  72. 42
      test/integration/lib/send-new-ui.js
  73. 13
      test/integration/lib/tx-list-items.js
  74. 6
      test/lib/mock-encryptor.js
  75. 2
      test/lib/util.js
  76. 4
      test/unit/actions/tx_test.js
  77. 56
      test/unit/app/controllers/blacklist-controller-test.js
  78. 2
      test/unit/app/controllers/currency-controller-test.js
  79. 12
      test/unit/app/controllers/infura-controller-test.js
  80. 25
      test/unit/app/controllers/metamask-controller-test.js
  81. 3
      test/unit/app/controllers/network-contoller-test.js
  82. 6
      test/unit/app/controllers/preferences-controller-test.js
  83. 238
      test/unit/app/controllers/transactions/nonce-tracker-test.js
  84. 84
      test/unit/app/controllers/transactions/pending-tx-test.js
  85. 5
      test/unit/app/controllers/transactions/recipient-blacklist-checker-test.js
  86. 87
      test/unit/app/controllers/transactions/tx-controller-test.js
  87. 2
      test/unit/app/controllers/transactions/tx-gas-util-test.js
  88. 2
      test/unit/app/controllers/transactions/tx-state-history-helper-test.js
  89. 2
      test/unit/app/controllers/transactions/tx-state-manager-test.js
  90. 2
      test/unit/app/edge-encryptor-test.js
  91. 2
      test/unit/migrations/migrator-test.js
  92. 57
      test/unit/ui/app/actions.spec.js
  93. 4
      test/unit/ui/etherscan-prefix-for-network.spec.js
  94. 105
      test/web3/index.html
  95. 209
      test/web3/schema.js
  96. 34
      test/web3/web3.js
  97. 17
      ui/app/components/app/account-panel.js
  98. 5
      ui/app/components/app/add-token-button/index.scss
  99. 17
      ui/app/components/app/app-header/index.scss
  100. 2
      ui/app/components/app/bn-as-decimal-input.js
  101. Some files were not shown because too many files have changed in this diff Show More

@ -1,24 +1,9 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
# Change these settings to your own preference
indent_style = space
indent_size = 2
[*.json]
indent_size = 2
# We recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

@ -133,7 +133,7 @@
"no-unneeded-ternary": [2, { "defaultAssignment": false }],
"no-unreachable": 2,
"no-unsafe-finally": 2,
"no-unused-vars": [2, { "vars": "all", "args": "none" }],
"no-unused-vars": [2, { "vars": "all", "args": "all", "argsIgnorePattern": "[_]+" }],
"no-useless-call": 2,
"no-useless-computed-key": 2,
"no-useless-constructor": 2,

@ -2,6 +2,68 @@
## Current Develop Branch
## 6.5.3 Thu May 16 2019
- [#6619](https://github.com/MetaMask/metamask-extension/pull/6619): bugfix: show extension window if locked regardless of approval
- [#6388](https://github.com/MetaMask/metamask-extension/pull/6388): Transactions/pending - check nonce against the network and mark as dropped if not included in a block
- [#6606](https://github.com/MetaMask/metamask-extension/pull/6606): Improve ENS Address Input
- [#6615](https://github.com/MetaMask/metamask-extension/pull/6615): Adds e2e test for removing imported accounts.
## 6.5.2 Wed May 15 2019
- [#6613](https://github.com/MetaMask/metamask-extension/pull/6613): Hardware Wallet Fix
## 6.5.1 Tue May 14 2019
- Fix bug where approve method would show a warning. #6602
- [#6593](https://github.com/MetaMask/metamask-extension/pull/6593): Fix wording of autoLogoutTimeLimitDescription
## 6.5.0 Fri May 10 2019
- [#6568](https://github.com/MetaMask/metamask-extension/pull/6568): feature: integrate gaba/PhishingController
- [#6490](https://github.com/MetaMask/metamask-extension/pull/6490): Redesign custom RPC form
- [#6558](https://github.com/MetaMask/metamask-extension/pull/6558): Adds auto logout with customizable time frame
- [#6578](https://github.com/MetaMask/metamask-extension/pull/6578): Fixes ability to send to token contract addresses
- [#6557](https://github.com/MetaMask/metamask-extension/pull/6557): Adds drag and drop functionality to seed phrase entry.
- [#6526](https://github.com/MetaMask/metamask-extension/pull/6526): Include token checksum address in prices lookup for token rates
- [#6502](https://github.com/MetaMask/metamask-extension/pull/6502): Add subheader to all settings subviews
- [#6501](https://github.com/MetaMask/metamask-extension/pull/6501): Improve confirm screen loading performance by fixing home screen rendering bug
## 6.4.1 Fri Apr 26 2019
- [#6521](https://github.com/MetaMask/metamask-extension/pull/6521): Revert "Adds 4byte registry fallback to getMethodData()" to fix stalling bug.
## 6.4.0 Wed Apr 17 2019
- [#6445](https://github.com/MetaMask/metamask-extension/pull/6445): * Move send to pages/
- [#6470](https://github.com/MetaMask/metamask-extension/pull/6470): update publishing.md with dev diagram
- [#6403](https://github.com/MetaMask/metamask-extension/pull/6403): Update to eth-method-registry@1.2.0
- [#6468](https://github.com/MetaMask/metamask-extension/pull/6468): Fix switcher height when Custom RPC is selected or loading
- [#6459](https://github.com/MetaMask/metamask-extension/pull/6459): feature: add Goerli support
- [#6444](https://github.com/MetaMask/metamask-extension/pull/6444): Fixes #6321 & #6421 - Add Localhost 8545 for network dropdown names
- [#6454](https://github.com/MetaMask/metamask-extension/pull/6454): Bump eth-contract-metadata
- [#6448](https://github.com/MetaMask/metamask-extension/pull/6448): Remove unneeded array cloning in getSendToAccounts selector
- [#6056](https://github.com/MetaMask/metamask-extension/pull/6056): repeated getSelectedAddress() func send.selectors.js removed
- [#6422](https://github.com/MetaMask/metamask-extension/pull/6422): Added Chrome limited site access solution doc
- [#6424](https://github.com/MetaMask/metamask-extension/pull/6424): feature: switch token pricing to CoinGecko API
- [#6428](https://github.com/MetaMask/metamask-extension/pull/6428): Don't inject web3 on sharefile.com
- [#6417](https://github.com/MetaMask/metamask-extension/pull/6417): Metrics updates
- [#6420](https://github.com/MetaMask/metamask-extension/pull/6420): Fix links to MetamaskInpageProvider in porting_to_new_environment.md
- [#6362](https://github.com/MetaMask/metamask-extension/pull/6362): Remove broken image walkthrough from metamaskbot comment
- [#6401](https://github.com/MetaMask/metamask-extension/pull/6401): metamask-controller - use improved provider-as-middleware utility
- [#6406](https://github.com/MetaMask/metamask-extension/pull/6406): remove user actions controller
- [#6399](https://github.com/MetaMask/metamask-extension/pull/6399): doc - publishing - typo fix
- [#6396](https://github.com/MetaMask/metamask-extension/pull/6396): pin eth-contract-metadata to last commit hash
- [#6397](https://github.com/MetaMask/metamask-extension/pull/6397): Change coinbase to wyre
- [#6395](https://github.com/MetaMask/metamask-extension/pull/6395): bump ledger and trezor keyring
- [#6389](https://github.com/MetaMask/metamask-extension/pull/6389): Fix display of gas chart on Ethereum networks
- [#6382](https://github.com/MetaMask/metamask-extension/pull/6382): Remove NoticeController
## 6.3.2 Mon Apr 8 2019
- [#6389](https://github.com/MetaMask/metamask-extension/pull/6389): Fix display of gas chart on ethereum networks
- [#6395](https://github.com/MetaMask/metamask-extension/pull/6395): Fixes for signing methods for ledger and trezor devices
- [#6397](https://github.com/MetaMask/metamask-extension/pull/6397): Fix Wyre link
## 6.3.1 Fri Mar 26 2019
- [#6353](https://github.com/MetaMask/metamask-extension/pull/6353): Open restore vault in full screen when clicked from popup
@ -15,7 +77,7 @@
- [#6302](https://github.com/MetaMask/metamask-extension/pull/6302): Replaces the coinbase link in the deposit modal with one for wyre
- [#6307](https://github.com/MetaMask/metamask-extension/pull/6307): Centre the notification in the current window
- [#6312](https://github.com/MetaMask/metamask-extension/pull/6312): Fixes popups not showing when screen size is odd
- [#6326](https://github.com/MetaMask/metamask-extension/pull/6326): Fix oversized loading overlay on gas customization modal.
- [#6326](https://github.com/MetaMask/metamask-extension/pull/6326): Fix oversized loading overlay on gas customization modal.
- [#6330](https://github.com/MetaMask/metamask-extension/pull/6330): Stop reloading dapps on network change allowing dapps to decide if it should refresh or not
- [#6332](https://github.com/MetaMask/metamask-extension/pull/6332): Enable mobile sync
- [#6333](https://github.com/MetaMask/metamask-extension/pull/6333): Redesign of the settings screen

@ -83,6 +83,9 @@
"address": {
"message": "Address"
},
"addNetwork": {
"message": "Add Network"
},
"advanced": {
"message": "Advanced"
},
@ -139,6 +142,9 @@
"approved": {
"message": "Approved"
},
"asset": {
"message": "Asset"
},
"attemptingConnect": {
"message": "Attempting to connect to blockchain."
},
@ -151,6 +157,12 @@
"attributions": {
"message": "Attributions"
},
"autoLogoutTimeLimit": {
"message": "Auto-Logout Timer (minutes)"
},
"autoLogoutTimeLimitDescription": {
"message": "Set the idle time in minutes before MetaMask will automatically log out"
},
"available": {
"message": "Available"
},
@ -182,6 +194,13 @@
"message": "must be greater than or equal to $1 and less than or equal to $2.",
"description": "helper for inputting hex as decimal input"
},
"blockExplorerUrl": {
"message": "Block Explorer"
},
"blockExplorerView": {
"message": "View account at $1",
"description": "$1 replaced by URL for custom block explorer"
},
"blockiesIdenticon": {
"message": "Use Blockies Identicon"
},
@ -221,6 +240,9 @@
"ok": {
"message": "Ok"
},
"optionalBlockExplorerUrl": {
"message": "Block Explorer URL (optional)"
},
"cancel": {
"message": "Cancel"
},
@ -236,6 +258,9 @@
"cancelN": {
"message": "Cancel all $1 transactions"
},
"chainId": {
"message": "Chain ID"
},
"classicInterface": {
"message": "Use classic interface"
},
@ -308,6 +333,12 @@
"connectingToRinkeby": {
"message": "Connecting to Rinkeby Test Network"
},
"connectingToLocalhost": {
"message": "Connecting to Localhost 8545"
},
"connectingToGoerli": {
"message": "Connecting to Goerli Test Network"
},
"connectingToUnknown": {
"message": "Connecting to Unknown Network"
},
@ -487,6 +518,9 @@
"edit": {
"message": "Edit"
},
"editNetwork": {
"message": "Edit Network"
},
"editAccountName": {
"message": "Edit Account Name"
},
@ -515,7 +549,7 @@
"message": "Be careful of phishing! MetaMask will never spontaneously ask for your seed phrase."
},
"endOfFlowMessage6": {
"message": "If you need to back your up seed phrase again, you can find it in Settings -> Security."
"message": "If you need to back up your seed phrase again, you can find it in Settings -> Security."
},
"endOfFlowMessage7": {
"message": "If you ever have questions or see something fishy, email support@metamask.io."
@ -919,9 +953,15 @@
"negativeETH": {
"message": "Can not send negative amounts of ETH."
},
"networkName": {
"message": "Network Name"
},
"networks": {
"message": "Networks"
},
"networkSettingsDescription": {
"message": "Add and edit custom RPC networks"
},
"nevermind": {
"message": "Nevermind"
},
@ -962,7 +1002,7 @@
"protectYourKeysMessage2": {
"message": "Keep your phrase safe. If you see something fishy, or you’re uncertain about a website, email support@metamask.io"
},
"rpcURL": {
"rpcUrl": {
"message": "New RPC URL"
},
"showAdvancedOptions": {
@ -1209,7 +1249,7 @@
"message": "Revert"
},
"remove": {
"message": "remove"
"message": "Remove"
},
"removeAccount": {
"message": "Remove account"
@ -1226,6 +1266,9 @@
"ropsten": {
"message": "Ropsten Test Network"
},
"goerli": {
"message": "Goerli Test Network"
},
"rpc": {
"message": "Custom RPC"
},
@ -1342,6 +1385,9 @@
"selectAnAccountHelp": {
"message": "Select the account to view in MetaMask"
},
"selectAnAsset": {
"message": "Select an Asset"
},
"selectAHigherGasFee": {
"message": "Select a higher gas fee to accelerate the processing of your transaction.*"
},
@ -1471,6 +1517,9 @@
"supportCenter": {
"message": "Visit our Support Center"
},
"symbol": {
"message": "Symbol"
},
"symbolBetweenZeroTwelve": {
"message": "Symbol must be between 0 and 12 characters."
},
@ -1693,9 +1742,15 @@
"viewAccount": {
"message": "View Account"
},
"viewOnCustomBlockExplorer": {
"message": "View at $1"
},
"viewOnEtherscan": {
"message": "View on Etherscan"
},
"viewNetworkInfo": {
"message": "View Network Info"
},
"visitWebSite": {
"message": "Visit our web site"
},

@ -11,6 +11,9 @@
"exposeDescription": {
"message": "Esporre gli account al sito Web corrente. Utile per dapps legacy."
},
"chartOnlyAvailableEth": {
"message": "Grafico disponibile solo per le reti Ethereum."
},
"confirmExpose": {
"message": "Sei sicuro di voler esporre i tuoi account al sito web corrente?"
},
@ -41,6 +44,15 @@
"providerRequestInfo": {
"message": "Il dominio elencato di seguito sta tentando di richiedere l'accesso all'API Ethereum in modo che possa interagire con la blockchain di Ethereum. Controlla sempre di essere sul sito corretto prima di approvare l'accesso a Ethereum."
},
"about": {
"message": "Informazioni"
},
"aboutSettingsDescription": {
"message": "Version, centro di supporto e contatti."
},
"aboutUs": {
"message": "Chi siamo"
},
"accept": {
"message": "Accetta"
},
@ -71,6 +83,15 @@
"address": {
"message": "Indirizzo"
},
"addNetwork": {
"message": "Aggiungi Rete"
},
"advanced": {
"message": "Avanzate"
},
"advancedSettingsDescription": {
"message": "Accedi alle funzionalità sviluppatore, download dei log di Stato, Reset Account, imposta reti di test e RPC personalizzata."
},
"advancedOptions": {
"message": "Opzioni Avanzate"
},
@ -89,8 +110,14 @@
"addAcquiredTokens": {
"message": "Aggiungi i token che hai acquistato usando MetaMask"
},
"advanced": {
"message": "Avanzato"
"agreeTermsOfService": {
"message": "Accetto i termini di servizio"
},
"allDone": {
"message": "Tutto Fatto"
},
"alreadyHaveSeedPhrase": {
"message": "No, ho già una frase seed"
},
"amount": {
"message": "Importo"
@ -115,6 +142,9 @@
"approved": {
"message": "Approvato"
},
"asset": {
"message": "Asset"
},
"attemptingConnect": {
"message": "Tentativo di connessione alla blockchain."
},
@ -127,6 +157,12 @@
"attributions": {
"message": "Attribuzioni"
},
"autoLogoutTimeLimit": {
"message": "Timer di Logout Automatico (minuti)"
},
"autoLogoutTimeLimitDescription": {
"message": "Imposta il tempo di inattività dopo il quale MetaMask fa il log out automaticamente"
},
"available": {
"message": "Disponibile"
},
@ -158,6 +194,13 @@
"message": "deve essere maggiore o uguale a $1 e minore o uguale a $2.",
"description": "aiuto per inserire un input esadecimale come decimale"
},
"blockExplorerUrl": {
"message": "Block Explorer"
},
"blockExplorerView": {
"message": "Visualizza account su $1",
"description": "$1 replaced by URL for custom block explorer"
},
"blockiesIdenticon": {
"message": "Usa le icone Blockie"
},
@ -179,6 +222,12 @@
"buyCoinbaseExplainer": {
"message": "Coinbase è il servizio più popolare al mondo per comprare e vendere Bitcoin, Ethereum e Litecoin."
},
"buyWithWyre": {
"message": "Compra ETH con Wyre"
},
"buyWithWyreDescription": {
"message": "Wyre ti consente di usare la carta di credito per depositare ETH direttamente nel tuo account MetaMask."
},
"buyCoinSwitch": {
"message": "Compra su CoinSwitch"
},
@ -191,6 +240,9 @@
"ok": {
"message": "Ok"
},
"optionalBlockExplorerUrl": {
"message": "URL del Block Explorer (opzionale)"
},
"cancel": {
"message": "Annulla"
},
@ -206,6 +258,9 @@
"cancelN": {
"message": "Annulla tutte le transazioni relative a $1"
},
"chainId": {
"message": "Chain ID"
},
"classicInterface": {
"message": "Usa l'interfaccia classica"
},
@ -224,6 +279,9 @@
"chromeRequiredForHardwareWallets": {
"message": "Devi usare MetaMask con Google Chrome per connettere il tuo Portafoglio Hardware"
},
"company": {
"message": "Azienda"
},
"confirm": {
"message": "Conferma"
},
@ -245,6 +303,9 @@
"confirmTransaction": {
"message": "Conferma Transazione"
},
"congratulations": {
"message": "Congratulazioni"
},
"connectHardwareWallet": {
"message": "Connetti Portafoglio Hardware"
},
@ -272,6 +333,12 @@
"connectingToRinkeby": {
"message": "Connessione alla Rete di test Rinkeby"
},
"connectingToLocalhost": {
"message": "Connessione a Localhost 8545"
},
"connectingToGoerli": {
"message": "Connessione alla Rete di Test Goerli"
},
"connectingToUnknown": {
"message": "Connessione ad una Rete Sconosciuta"
},
@ -287,6 +354,9 @@
"continueToCoinbase": {
"message": "Continua su Coinbase"
},
"continueToWyre": {
"message": "Continua su Wyre"
},
"continueToCoinSwitch": {
"message": "Continua su CoinSwitch"
},
@ -314,6 +384,12 @@
"copyAddress": {
"message": "Copia l'indirizzo"
},
"copyTransactionId": {
"message": "Copia ID Transazione"
},
"copiedTransactionId": {
"message": "ID Transazione Copiato"
},
"copyToClipboard": {
"message": "Copia negli appunti"
},
@ -329,6 +405,9 @@
"createAccount": {
"message": "Crea Account"
},
"createAWallet": {
"message": "Crea un Wallet"
},
"createDen": {
"message": "Crea"
},
@ -439,6 +518,9 @@
"edit": {
"message": "Modifica"
},
"editNetwork": {
"message": "Modifica Rete"
},
"editAccountName": {
"message": "Modifica Nome Account"
},
@ -451,6 +533,30 @@
"encryptNewDen": {
"message": "Cripta il tuo nuovo DEN"
},
"endOfFlowMessage1": {
"message": "Hai passato il test - tieni la tua frase seed al sicuro, è tua responsabilità!"
},
"endOfFlowMessage2": {
"message": "Suggerimenti su come tenerla al sicuro"
},
"endOfFlowMessage3": {
"message": "Salva un backup in più di un posto."
},
"endOfFlowMessage4": {
"message": "Non condividerla mai con nessuno."
},
"endOfFlowMessage5": {
"message": "Stai attento al phishing! MetaMask non ti chiederà mai spontaneamente la tua frase seed."
},
"endOfFlowMessage6": {
"message": "Se vorrai fare nuovamente un backup della frase, la puoi trovare in Impostazioni -> Sicurezza & Privacy."
},
"endOfFlowMessage7": {
"message": "Se hai delle domande o vedi delle attività sospette, manda una mail a support@metamask.io."
},
"endOfFlowMessage8": {
"message": "MetaMask non può recuperare la tua frase seed. Impara di più."
},
"ensNameNotFound": {
"message": "Nome ENS non trovato"
},
@ -571,6 +677,12 @@
"gasPriceRequired": {
"message": "Prezzo Gas Richiesto"
},
"general": {
"message": "Generale"
},
"generalSettingsDescription": {
"message": "Conversione moneta, moneta primaria, lingua, icone blockie"
},
"generatingTransaction": {
"message": "Generando la transazione"
},
@ -584,10 +696,16 @@
"getHelp": {
"message": "Aiuto."
},
"getStarted": {
"message": "Inizia"
},
"greaterThanMin": {
"message": "deve essere maggiore o uguale a $1.",
"description": "aiuto per inserire un input esadecimale come decimale"
},
"happyToSeeYou": {
"message": "Siamo contenti di vederti."
},
"hardware": {
"message": "hardware"
},
@ -650,6 +768,12 @@
"importDen": {
"message": "Importa un DEN Esistente"
},
"importWallet": {
"message": "Importa Portafoglio"
},
"importYourExisting": {
"message": "Importa il tuo portafoglio esistente usando la tua frase seed a 12 parole"
},
"imported": {
"message": "Importato",
"description": "stato che conferma che un account è stato totalmente caricato nel portachiavi"
@ -687,6 +811,9 @@
"knownAddressRecipient": {
"message": "Indirizzo del contratto conosciuto."
},
"invalidAddressRecipientNotEthNetwork": {
"message": "Non rete ETH, inserisci caratteri minuscoli"
},
"invalidGasParams": {
"message": "Parametri del Gas non validi"
},
@ -727,10 +854,16 @@
"ledgerAccountRestriction": {
"message": "E' necessario utilizzare l'ultimo account prima di poterne aggiungere uno nuovo."
},
"legal": {
"message": "Informazioni Legali"
},
"lessThanMax": {
"message": "deve essere minore o uguale a $1.",
"description": "aiuto per inserire un input esadecimale come decimale"
},
"letsGoSetUp": {
"message": "Si, iniziamo!"
},
"likeToAddTokens": {
"message": "Vorresti aggiungere questi token?"
},
@ -794,6 +927,12 @@
"minutesShorthand": {
"message": "Min"
},
"mobileSyncTitle": {
"message": "Sincronizza account con il dispositivo mobile"
},
"mobileSyncText": {
"message": "Per favore inserisci la password per confermare che sei te!"
},
"myAccounts": {
"message": "Miei Account"
},
@ -814,9 +953,15 @@
"negativeETH": {
"message": "Non puoi inviare una quantità di ETH negativa."
},
"networkName": {
"message": "Nome Rete"
},
"networks": {
"message": "Reti"
},
"networkSettingsDescription": {
"message": "Aggiungi e modifica reti RPC personalizzate"
},
"nevermind": {
"message": "Non importa"
},
@ -842,7 +987,22 @@
"newNetwork": {
"message": "Nuova Rete"
},
"rpcURL": {
"newToMetaMask": {
"message": "Nuovo a MetaMask?"
},
"noAlreadyHaveSeed": {
"message": "No, ho già una frase seed"
},
"protectYourKeys": {
"message": "Proteggi le tue chiavi!"
},
"protectYourKeysMessage1": {
"message": "Stai attento con la tua frase seed - ci sono stati report di siti web che hanno tentato di imitare MetaMask. MetaMask non ti chiederà mai la tua frase seed!"
},
"protectYourKeysMessage2": {
"message": "Tieni la tua frase al sicuro. Se vedi qualcosa di sospetto, o non sei sicuro di un sito web, manda una mail a support@metamask.io"
},
"rpcUrl": {
"message": "Nuovo URL RPC"
},
"showAdvancedOptions": {
@ -884,6 +1044,9 @@
"noTransactions": {
"message": "Nessuna Transazione"
},
"notEnoughGas": {
"message": "Gas Non Sufficiente"
},
"notFound": {
"message": "Non Trovata"
},
@ -934,6 +1097,12 @@
"originalTotal": {
"message": "Totale Precedente"
},
"participateInMetaMetrics": {
"message": "Participa in MetaMetrics"
},
"participateInMetaMetricsDescription": {
"message": "Participa in MetaMetrics per aiutarci a rendere MetaMask migliore"
},
"password": {
"message": "Password"
},
@ -1097,6 +1266,9 @@
"ropsten": {
"message": "Rete di test Ropsten"
},
"goerli": {
"message": "Rete di test Goerli"
},
"rpc": {
"message": "RPC Personalizzata"
},
@ -1147,6 +1319,12 @@
"secretPhrase": {
"message": "Inserisci la tua frase segreta di dodici parole per ripristinare la cassaforte."
},
"securityAndPrivacy": {
"message": "Sicurezza & Privacy"
},
"securitySettingsDescription": {
"message": "Impostazioni sulla Privacy e sulla frase seed del portafoglio"
},
"secondsShorthand": {
"message": "Sec"
},
@ -1207,6 +1385,9 @@
"selectAnAccountHelp": {
"message": "Selezione l'account da visualizzare in MetaMask"
},
"selectAnAsset": {
"message": "Seleziona un Asset"
},
"selectAHigherGasFee": {
"message": "Seleziona un costo in gas maggiore per accelerare l'elaborazione della transazione.*"
},
@ -1229,7 +1410,13 @@
"message": "Controlli gas avanzati"
},
"showAdvancedGasInlineDescription": {
"message": "Seleziona qui per visualizzare i controlli su prezzo e limite del gas nelle schermate di invio e conferma."
"message": "Seleziona per visualizzare i controlli su prezzo e limite del gas nelle schermate di invio e conferma."
},
"showFiatConversionInTestnets": {
"message": "Mostra conversione nelle reti di test"
},
"showFiatConversionInTestnetsDescription": {
"message": "Seleziona se vuoi vedere la conversione in valuta fiat nelle reti di test"
},
"showPrivateKeys": {
"message": "Mostra Chiave Privata"
@ -1330,9 +1517,33 @@
"supportCenter": {
"message": "Visita il nostro Centro di Supporto"
},
"symbol": {
"message": "Simbolo"
},
"symbolBetweenZeroTwelve": {
"message": "Il simbolo deve essere lungo tra 0 e 12 caratteri."
},
"syncWithMobile": {
"message": "Sincronizza con dispositivo mobile"
},
"syncWithMobileTitle": {
"message": "Sincronizza con dispositivo mobile"
},
"syncWithMobileDesc": {
"message": "Puoi sincronizzare i tuoi account e le tue informazioni con il tuo dispositivo mobile. Apri l'app di MetaMask, vai su \"Impostazioni\" e tocca \"Sincronizza da Estensione sul Browser\""
},
"syncWithMobileDescNewUsers": {
"message": "Se hai appena aperto l'app di MetaMask per la prima volta, segui i passaggi sul tuo telefono."
},
"syncWithMobileScanThisCode": {
"message": "Scansiona questo codice con l'app di MetaMask"
},
"syncWithMobileBeCareful": {
"message": "Assicurati che nessun'altro stia guardando al tuo schermo quando scansioni questo codice"
},
"syncWithMobileComplete": {
"message": "I tuoi dati sono stati sincronizzati con successo. Goditi l'app di MetaMask!"
},
"takesTooLong": {
"message": "Ci sta mettendo troppo?"
},
@ -1342,6 +1553,9 @@
"testFaucet": {
"message": "Prova Faucet"
},
"thisWillCreate": {
"message": "Questo creerà un nuovo portafoglio e frase seed"
},
"tips": {
"message": "Suggerimenti"
},
@ -1364,6 +1578,9 @@
"tokenBalance": {
"message": "Bilancio Token:"
},
"tokenContractAddress": {
"message": "Indirizzo Contratto Token"
},
"tokenSelection": {
"message": "Cerca un token o seleziona dalla lista di token più popolari."
},
@ -1525,9 +1742,15 @@
"viewAccount": {
"message": "Vedi Account"
},
"viewOnCustomBlockExplorer": {
"message": "Vedi su $1"
},
"viewOnEtherscan": {
"message": "Vedi su Etherscan"
},
"viewNetworkInfo": {
"message": "Visualizza Informazioni Rete"
},
"visitWebSite": {
"message": "Visita il nostro sito web"
},
@ -1573,6 +1796,9 @@
"yourUniqueAccountImageDescription2": {
"message": "Vedrai questa immagine ogni volta che dovrai confermare una transazione."
},
"yourUniqueAccountImageDescription3": {
"message": "MetaMask non ti chiederà mai la tua frase seed!"
},
"zeroGasPriceOnSpeedUpError": {
"message": "Prezzo del gas maggiore di zero"
}

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

@ -1,18 +1,17 @@
const fs = require('fs')
const path = require('path')
const pump = require('pump')
const log = require('loglevel')
const Dnode = require('dnode')
const querystring = require('querystring')
const LocalMessageDuplexStream = require('post-message-stream')
const PongStream = require('ping-pong-stream/pong')
const ObjectMultiplex = require('obj-multiplex')
const extension = require('extensionizer')
const PortStream = require('extension-port-stream')
const {Transform: TransformStream} = require('stream')
const inpageContent = fs.readFileSync(path.join(__dirname, '..', '..', 'dist', 'chrome', 'inpage.js')).toString()
const inpageSuffix = '//# sourceURL=' + extension.extension.getURL('inpage.js') + '\n'
const inpageBundle = inpageContent + inpageSuffix
let isEnabled = false
// Eventually this streaming injection could be replaced with:
// https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Language_Bindings/Components.utils.exportFunction
@ -23,9 +22,7 @@ let isEnabled = false
if (shouldInjectWeb3()) {
injectScript(inpageBundle)
setupStreams()
listenForProviderRequest()
checkPrivacyMode()
start()
}
/**
@ -46,149 +43,108 @@ function injectScript (content) {
}
}
/**
* Sets up the stream communication and submits site metadata
*
*/
async function start () {
await setupStreams()
await domIsReady()
}
/**
* Sets up two-way communication streams between the
* browser extension and local per-page browser context
* browser extension and local per-page browser context.
*
*/
function setupStreams () {
// setup communication to page and plugin
async function setupStreams () {
// the transport-specific streams for communication between inpage and background
const pageStream = new LocalMessageDuplexStream({
name: 'contentscript',
target: 'inpage',
})
const pluginPort = extension.runtime.connect({ name: 'contentscript' })
const pluginStream = new PortStream(pluginPort)
const extensionPort = extension.runtime.connect({ name: 'contentscript' })
const extensionStream = new PortStream(extensionPort)
// Filter out selectedAddress until this origin is enabled
const approvalTransform = new TransformStream({
objectMode: true,
transform: (data, _, done) => {
if (typeof data === 'object' && data.name && data.name === 'publicConfig' && !isEnabled) {
data.data.selectedAddress = undefined
}
done(null, { ...data })
},
})
// create and connect channel muxers
// so we can handle the channels individually
const pageMux = new ObjectMultiplex()
pageMux.setMaxListeners(25)
const extensionMux = new ObjectMultiplex()
extensionMux.setMaxListeners(25)
// forward communication plugin->inpage
pump(
pageMux,
pageStream,
pluginStream,
approvalTransform,
pageStream,
(err) => logStreamDisconnectWarning('MetaMask Contentscript Forwarding', err)
pageMux,
(err) => logStreamDisconnectWarning('MetaMask Inpage Multiplex', err)
)
// setup local multistream channels
const mux = new ObjectMultiplex()
mux.setMaxListeners(25)
pump(
mux,
pageStream,
mux,
(err) => logStreamDisconnectWarning('MetaMask Inpage', err)
)
pump(
mux,
pluginStream,
mux,
(err) => logStreamDisconnectWarning('MetaMask Background', err)
extensionMux,
extensionStream,
extensionMux,
(err) => logStreamDisconnectWarning('MetaMask Background Multiplex', err)
)
// connect ping stream
const pongStream = new PongStream({ objectMode: true })
pump(
mux,
pongStream,
mux,
(err) => logStreamDisconnectWarning('MetaMask PingPongStream', err)
)
// forward communication across inpage-background for these channels only
forwardTrafficBetweenMuxers('provider', pageMux, extensionMux)
forwardTrafficBetweenMuxers('publicConfig', pageMux, extensionMux)
// connect phishing warning stream
const phishingStream = mux.createStream('phishing')
// connect "phishing" channel to warning system
const phishingStream = extensionMux.createStream('phishing')
phishingStream.once('data', redirectToPhishingWarning)
// ignore unused channels (handled by background, inpage)
mux.ignoreStream('provider')
mux.ignoreStream('publicConfig')
// connect "publicApi" channel to submit page metadata
const publicApiStream = extensionMux.createStream('publicApi')
const background = await setupPublicApi(publicApiStream)
return { background }
}
/**
* Establishes listeners for requests to fully-enable the provider from the dapp context
* and for full-provider approvals and rejections from the background script context. Dapps
* should not post messages directly and should instead call provider.enable(), which
* handles posting these messages internally.
*/
function listenForProviderRequest () {
window.addEventListener('message', ({ source, data }) => {
if (source !== window || !data || !data.type) { return }
switch (data.type) {
case 'ETHEREUM_ENABLE_PROVIDER':
extension.runtime.sendMessage({
action: 'init-provider-request',
force: data.force,
origin: source.location.hostname,
siteImage: getSiteIcon(source),
siteTitle: getSiteName(source),
})
break
case 'ETHEREUM_IS_APPROVED':
extension.runtime.sendMessage({
action: 'init-is-approved',
origin: source.location.hostname,
})
break
case 'METAMASK_IS_UNLOCKED':
extension.runtime.sendMessage({
action: 'init-is-unlocked',
})
break
}
})
function forwardTrafficBetweenMuxers (channelName, muxA, muxB) {
const channelA = muxA.createStream(channelName)
const channelB = muxB.createStream(channelName)
pump(
channelA,
channelB,
channelA,
(err) => logStreamDisconnectWarning(`MetaMask muxed traffic for channel "${channelName}" failed.`, err)
)
}
extension.runtime.onMessage.addListener(({ action = '', isApproved, caching, isUnlocked, selectedAddress }) => {
switch (action) {
case 'approve-provider-request':
isEnabled = true
window.postMessage({ type: 'ethereumprovider', selectedAddress }, '*')
break
case 'approve-legacy-provider-request':
isEnabled = true
window.postMessage({ type: 'ethereumproviderlegacy', selectedAddress }, '*')
break
case 'reject-provider-request':
window.postMessage({ type: 'ethereumprovider', error: 'User denied account authorization' }, '*')
break
case 'answer-is-approved':
window.postMessage({ type: 'ethereumisapproved', isApproved, caching }, '*')
break
case 'answer-is-unlocked':
window.postMessage({ type: 'metamaskisunlocked', isUnlocked }, '*')
break
case 'metamask-set-locked':
isEnabled = false
window.postMessage({ type: 'metamasksetlocked' }, '*')
break
case 'ethereum-ping-success':
window.postMessage({ type: 'ethereumpingsuccess' }, '*')
break
case 'ethereum-ping-error':
window.postMessage({ type: 'ethereumpingerror' }, '*')
async function setupPublicApi (outStream) {
const api = {
getSiteMetadata: (cb) => cb(null, getSiteMetadata()),
}
const dnode = Dnode(api)
pump(
outStream,
dnode,
outStream,
(err) => {
// report any error
if (err) log.error(err)
}
})
)
const background = await new Promise(resolve => dnode.once('remote', resolve))
return background
}
/**
* Checks if MetaMask is currently operating in "privacy mode", meaning
* dapps must call ethereum.enable in order to access user accounts
* Gets site metadata and returns it
*
*/
function checkPrivacyMode () {
extension.runtime.sendMessage({ action: 'init-privacy-request' })
function getSiteMetadata () {
// get metadata
const metadata = {
name: getSiteName(window),
icon: getSiteIcon(window),
}
return metadata
}
/**
* Error handler for page to plugin stream disconnections
* Error handler for page to extension stream disconnections
*
* @param {string} remoteLabel Remote stream name
* @param {Error} err Stream connection error
@ -275,6 +231,7 @@ function blacklistedDomainCheck () {
'harbourair.com',
'ani.gamer.com.tw',
'blueskybooking.com',
'sharefile.com',
]
const currentUrl = window.location.href
let currentRegex
@ -300,6 +257,10 @@ function redirectToPhishingWarning () {
})}`
}
/**
* Extracts a name for the site from the DOM
*/
function getSiteName (window) {
const document = window.document
const siteName = document.querySelector('head > meta[property="og:site_name"]')
@ -315,6 +276,9 @@ function getSiteName (window) {
return document.title
}
/**
* Extracts an icon for the site from the DOM
*/
function getSiteIcon (window) {
const document = window.document
@ -332,3 +296,13 @@ function getSiteIcon (window) {
return null
}
/**
* Returns a promise that resolves when the DOM is loaded (does not wait for images to load)
*/
async function domIsReady () {
// already loaded
if (['interactive', 'complete'].includes(document.readyState)) return
// wait for load
await new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve, { once: true }))
}

@ -0,0 +1,73 @@
const ObservableStore = require('obs-store')
const extend = require('xtend')
class AppStateController {
/**
* @constructor
* @param opts
*/
constructor (opts = {}) {
const {initState, onInactiveTimeout, preferencesStore} = opts
const {preferences} = preferencesStore.getState()
this.onInactiveTimeout = onInactiveTimeout || (() => {})
this.store = new ObservableStore(extend({
timeoutMinutes: 0,
}, initState))
this.timer = null
preferencesStore.subscribe(state => {
this._setInactiveTimeout(state.preferences.autoLogoutTimeLimit)
})
this._setInactiveTimeout(preferences.autoLogoutTimeLimit)
}
/**
* Sets the last active time to the current time
* @return {void}
*/
setLastActiveTime () {
this._resetTimer()
}
/**
* Sets the inactive timeout for the app
* @param {number} timeoutMinutes the inactive timeout in minutes
* @return {void}
* @private
*/
_setInactiveTimeout (timeoutMinutes) {
this.store.putState({
timeoutMinutes,
})
this._resetTimer()
}
/**
* Resets the internal inactive timer
*
* If the {@code timeoutMinutes} state is falsy (i.e., zero) then a new
* timer will not be created.
*
* @return {void}
* @private
*/
_resetTimer () {
const {timeoutMinutes} = this.store.getState()
if (this.timer) {
clearTimeout(this.timer)
}
if (!timeoutMinutes) {
return
}
this.timer = setTimeout(() => this.onInactiveTimeout(), timeoutMinutes * 60 * 1000)
}
}
module.exports = AppStateController

@ -68,7 +68,7 @@ class BalanceController {
_registerUpdates () {
const update = this.updateBalance.bind(this)
this.txController.on('tx:status-update', (txId, status) => {
this.txController.on('tx:status-update', (_, status) => {
switch (status) {
case 'submitted':
case 'confirmed':

@ -1,136 +0,0 @@
const ObservableStore = require('obs-store')
const extend = require('xtend')
const PhishingDetector = require('eth-phishing-detect/src/detector')
const log = require('loglevel')
// compute phishing lists
const PHISHING_DETECTION_CONFIG = require('eth-phishing-detect/src/config.json')
// every four minutes
const POLLING_INTERVAL = 4 * 60 * 1000
class BlacklistController {
/**
* Responsible for polling for and storing an up to date 'eth-phishing-detect' config.json file, while
* exposing a method that can check whether a given url is a phishing attempt. The 'eth-phishing-detect'
* config.json file contains a fuzzylist, whitelist and blacklist.
*
*
* @typedef {Object} BlacklistController
* @param {object} opts Overrides the defaults for the initial state of this.store
* @property {object} store The the store of the current phishing config
* @property {object} store.phishing Contains fuzzylist, whitelist and blacklist arrays. @see
* {@link https://github.com/MetaMask/eth-phishing-detect/blob/master/src/config.json}
* @property {object} _phishingDetector The PhishingDetector instantiated by passing store.phishing to
* PhishingDetector.
* @property {object} _phishingUpdateIntervalRef Id of the interval created to periodically update the blacklist
*
*/
constructor (opts = {}) {
const initState = extend({
phishing: PHISHING_DETECTION_CONFIG,
whitelist: [],
}, opts.initState)
this.store = new ObservableStore(initState)
// phishing detector
this._phishingDetector = null
this._setupPhishingDetector(initState.phishing)
// polling references
this._phishingUpdateIntervalRef = null
}
/**
* Adds the given hostname to the runtime whitelist
* @param {string} hostname the hostname to whitelist
*/
whitelistDomain (hostname) {
if (!hostname) {
return
}
const { whitelist } = this.store.getState()
this.store.updateState({
whitelist: [...new Set([hostname, ...whitelist])],
})
}
/**
* Given a url, returns the result of checking if that url is in the store.phishing blacklist
*
* @param {string} hostname The hostname portion of a url; the one that will be checked against the white and
* blacklists of store.phishing
* @returns {boolean} Whether or not the passed hostname is on our phishing blacklist
*
*/
checkForPhishing (hostname) {
if (!hostname) return false
const { whitelist } = this.store.getState()
if (whitelist.some((e) => e === hostname)) {
return false
}
const { result } = this._phishingDetector.check(hostname)
return result
}
/**
* Queries `https://api.infura.io/v2/blacklist` for an updated blacklist config. This is passed to this._phishingDetector
* to update our phishing detector instance, and is updated in the store. The new phishing config is returned
*
*
* @returns {Promise<object>} Promises the updated blacklist config for the phishingDetector
*
*/
async updatePhishingList () {
// make request
let response
try {
response = await fetch('https://api.infura.io/v2/blacklist')
} catch (err) {
log.error(new Error(`BlacklistController - failed to fetch blacklist:\n${err.stack}`))
return
}
// parse response
let rawResponse
let phishing
try {
const rawResponse = await response.text()
phishing = JSON.parse(rawResponse)
} catch (err) {
log.error(new Error(`BlacklistController - failed to parse blacklist:\n${rawResponse}`))
return
}
// update current blacklist
this.store.updateState({ phishing })
this._setupPhishingDetector(phishing)
return phishing
}
/**
* Initiates the updating of the local blacklist at a set interval. The update is done via this.updatePhishingList().
* Also, this method store a reference to that interval at this._phishingUpdateIntervalRef
*
*/
scheduleUpdates () {
if (this._phishingUpdateIntervalRef) return
this.updatePhishingList()
this._phishingUpdateIntervalRef = setInterval(() => {
this.updatePhishingList()
}, POLLING_INTERVAL)
}
/**
* Sets this._phishingDetector to a new PhishingDetector instance.
* @see {@link https://github.com/MetaMask/eth-phishing-detect}
*
* @private
* @param {object} config A config object like that found at {@link https://github.com/MetaMask/eth-phishing-detect/blob/master/src/config.json}
*
*/
_setupPhishingDetector (config) {
this._phishingDetector = new PhishingDetector(config)
}
}
module.exports = BlacklistController

@ -1,19 +0,0 @@
const BlockTracker = require('eth-block-tracker')
/**
* Creates a block tracker that sends platform events on success and failure
*/
module.exports = function createBlockTracker (args, platform) {
const blockTracker = new BlockTracker(args)
blockTracker.on('latest', () => {
if (platform && platform.sendMessage) {
platform.sendMessage({ action: 'ethereum-ping-success' })
}
})
blockTracker.on('error', () => {
if (platform && platform.sendMessage) {
platform.sendMessage({ action: 'ethereum-ping-error' })
}
})
return blockTracker
}

@ -7,14 +7,14 @@ const createInflightMiddleware = require('eth-json-rpc-middleware/inflight-cache
const createBlockTrackerInspectorMiddleware = require('eth-json-rpc-middleware/block-tracker-inspector')
const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware')
const createInfuraMiddleware = require('eth-json-rpc-infura')
const createBlockTracker = require('./createBlockTracker')
const BlockTracker = require('eth-block-tracker')
module.exports = createInfuraClient
function createInfuraClient ({ network, platform }) {
function createInfuraClient ({ network }) {
const infuraMiddleware = createInfuraMiddleware({ network, maxAttempts: 5, source: 'metamask' })
const infuraProvider = providerFromMiddleware(infuraMiddleware)
const blockTracker = createBlockTracker({ provider: infuraProvider }, platform)
const blockTracker = new BlockTracker({ provider: infuraProvider })
const networkMiddleware = mergeMiddleware([
createNetworkAndChainIdMiddleware({ network }),
@ -49,6 +49,10 @@ function createNetworkAndChainIdMiddleware ({ network }) {
netId = '42'
chainId = '0x2a'
break
case 'goerli':
netId = '5'
chainId = '0x05'
break
default:
throw new Error(`createInfuraClient - unknown network "${network}"`)
}

@ -5,14 +5,14 @@ const createBlockCacheMiddleware = require('eth-json-rpc-middleware/block-cache'
const createInflightMiddleware = require('eth-json-rpc-middleware/inflight-cache')
const createBlockTrackerInspectorMiddleware = require('eth-json-rpc-middleware/block-tracker-inspector')
const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware')
const createBlockTracker = require('./createBlockTracker')
const BlockTracker = require('eth-block-tracker')
module.exports = createJsonRpcClient
function createJsonRpcClient ({ rpcUrl, platform }) {
function createJsonRpcClient ({ rpcUrl }) {
const fetchMiddleware = createFetchMiddleware({ rpcUrl })
const blockProvider = providerFromMiddleware(fetchMiddleware)
const blockTracker = createBlockTracker({ provider: blockProvider }, platform)
const blockTracker = new BlockTracker({ provider: blockProvider })
const networkMiddleware = mergeMiddleware([
createBlockRefRewriteMiddleware({ blockTracker }),

@ -3,14 +3,14 @@ const createFetchMiddleware = require('eth-json-rpc-middleware/fetch')
const createBlockRefRewriteMiddleware = require('eth-json-rpc-middleware/block-ref-rewrite')
const createBlockTrackerInspectorMiddleware = require('eth-json-rpc-middleware/block-tracker-inspector')
const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware')
const createBlockTracker = require('./createBlockTracker')
const BlockTracker = require('eth-block-tracker')
module.exports = createLocalhostClient
function createLocalhostClient ({ platform }) {
function createLocalhostClient () {
const fetchMiddleware = createFetchMiddleware({ rpcUrl: 'http://localhost:8545/' })
const blockProvider = providerFromMiddleware(fetchMiddleware)
const blockTracker = createBlockTracker({ provider: blockProvider, pollingInterval: 1000 }, platform)
const blockTracker = new BlockTracker({ provider: blockProvider, pollingInterval: 1000 })
const networkMiddleware = mergeMiddleware([
createBlockRefRewriteMiddleware({ blockTracker }),

@ -3,16 +3,19 @@ const RINKEBY = 'rinkeby'
const KOVAN = 'kovan'
const MAINNET = 'mainnet'
const LOCALHOST = 'localhost'
const GOERLI = 'goerli'
const MAINNET_CODE = 1
const ROPSTEN_CODE = 3
const RINKEYBY_CODE = 4
const KOVAN_CODE = 42
const GOERLI_CODE = 5
const ROPSTEN_DISPLAY_NAME = 'Ropsten'
const RINKEBY_DISPLAY_NAME = 'Rinkeby'
const KOVAN_DISPLAY_NAME = 'Kovan'
const MAINNET_DISPLAY_NAME = 'Main Ethereum Network'
const GOERLI_DISPLAY_NAME = 'Goerli'
module.exports = {
ROPSTEN,
@ -20,12 +23,15 @@ module.exports = {
KOVAN,
MAINNET,
LOCALHOST,
GOERLI,
MAINNET_CODE,
ROPSTEN_CODE,
RINKEYBY_CODE,
KOVAN_CODE,
GOERLI_CODE,
ROPSTEN_DISPLAY_NAME,
RINKEBY_DISPLAY_NAME,
KOVAN_DISPLAY_NAME,
MAINNET_DISPLAY_NAME,
GOERLI_DISPLAY_NAME,
}

@ -20,8 +20,9 @@ const {
KOVAN,
MAINNET,
LOCALHOST,
GOERLI,
} = require('./enums')
const INFURA_PROVIDER_TYPES = [ROPSTEN, RINKEBY, KOVAN, MAINNET]
const INFURA_PROVIDER_TYPES = [ROPSTEN, RINKEBY, KOVAN, MAINNET, GOERLI]
const env = process.env.METAMASK_ENV
const METAMASK_DEBUG = process.env.METAMASK_DEBUG
@ -45,9 +46,8 @@ const defaultNetworkConfig = {
module.exports = class NetworkController extends EventEmitter {
constructor (opts = {}, platform) {
constructor (opts = {}) {
super()
this.platform = platform
// parse options
const providerConfig = opts.provider || defaultProviderConfig
@ -129,21 +129,22 @@ module.exports = class NetworkController extends EventEmitter {
})
}
setRpcTarget (rpcTarget, chainId, ticker = 'ETH', nickname = '') {
setRpcTarget (rpcTarget, chainId, ticker = 'ETH', nickname = '', rpcPrefs) {
const providerConfig = {
type: 'rpc',
rpcTarget,
chainId,
ticker,
nickname,
rpcPrefs,
}
this.providerConfig = providerConfig
}
async setProviderType (type) {
async setProviderType (type, rpcTarget = '', 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 }
const providerConfig = { type, rpcTarget, ticker, nickname }
this.providerConfig = providerConfig
}
@ -189,7 +190,7 @@ module.exports = class NetworkController extends EventEmitter {
_configureInfuraProvider ({ type }) {
log.info('NetworkController - configureInfuraProvider', type)
const networkClient = createInfuraClient({ network: type, platform: this.platform })
const networkClient = createInfuraClient({ network: type })
this._setNetworkClient(networkClient)
// setup networkConfig
var settings = {
@ -200,13 +201,13 @@ module.exports = class NetworkController extends EventEmitter {
_configureLocalhostProvider () {
log.info('NetworkController - configureLocalhostProvider')
const networkClient = createLocalhostClient({ platform: this.platform })
const networkClient = createLocalhostClient()
this._setNetworkClient(networkClient)
}
_configureStandardProvider ({ rpcUrl, chainId, ticker, nickname }) {
log.info('NetworkController - configureStandardProvider', rpcUrl)
const networkClient = createJsonRpcClient({ rpcUrl, platform: this.platform })
const networkClient = createJsonRpcClient({ rpcUrl })
// hack to add a 'rpc' network with chainId
networks.networkList['rpc'] = {
chainId: chainId,

@ -3,13 +3,16 @@ const {
RINKEBY,
KOVAN,
MAINNET,
GOERLI,
ROPSTEN_CODE,
RINKEYBY_CODE,
KOVAN_CODE,
GOERLI_CODE,
ROPSTEN_DISPLAY_NAME,
RINKEBY_DISPLAY_NAME,
KOVAN_DISPLAY_NAME,
MAINNET_DISPLAY_NAME,
GOERLI_DISPLAY_NAME,
} = require('./enums')
const networkToNameMap = {
@ -17,9 +20,11 @@ const networkToNameMap = {
[RINKEBY]: RINKEBY_DISPLAY_NAME,
[KOVAN]: KOVAN_DISPLAY_NAME,
[MAINNET]: MAINNET_DISPLAY_NAME,
[GOERLI]: GOERLI_DISPLAY_NAME,
[ROPSTEN_CODE]: ROPSTEN_DISPLAY_NAME,
[RINKEYBY_CODE]: RINKEBY_DISPLAY_NAME,
[KOVAN_CODE]: KOVAN_DISPLAY_NAME,
[GOERLI_CODE]: GOERLI_DISPLAY_NAME,
}
const getNetworkDisplayName = key => networkToNameMap[key]

@ -117,6 +117,14 @@ class PreferencesController {
return metaMetricsId
}
getMetaMetricsId () {
return this.store.getState().metaMetricsId
}
getParticipateInMetaMetrics () {
return this.store.getState().participateInMetaMetrics
}
setMetaMetricsSendCount (val) {
this.store.updateState({ metaMetricsSendCount: val })
}
@ -331,7 +339,7 @@ class PreferencesController {
}
removeSuggestedTokens () {
return new Promise((resolve, reject) => {
return new Promise((resolve) => {
this.store.updateState({ suggestedTokens: {} })
resolve({})
})
@ -388,7 +396,7 @@ class PreferencesController {
const newEntry = { address, symbol, decimals }
const tokens = this.store.getState().tokens
const assetImages = this.getAssetImages()
const previousEntry = tokens.find((token, index) => {
const previousEntry = tokens.find((token) => {
return token.address === address
})
const previousIndex = tokens.indexOf(previousEntry)
@ -453,7 +461,7 @@ class PreferencesController {
*
*/
setCurrentAccountTab (currentAccountTab) {
return new Promise((resolve, reject) => {
return new Promise((resolve) => {
this.store.updateState({ currentAccountTab })
resolve()
})
@ -480,8 +488,8 @@ class PreferencesController {
rpcList[index] = updatedRpc
this.store.updateState({ frequentRpcListDetail: rpcList })
} else {
const { rpcUrl, chainId, ticker, nickname } = newRpcDetails
return this.addToFrequentRpcList(rpcUrl, chainId, ticker, nickname)
const { rpcUrl, chainId, ticker, nickname, rpcPrefs = {} } = newRpcDetails
return this.addToFrequentRpcList(rpcUrl, chainId, ticker, nickname, rpcPrefs)
}
return Promise.resolve(rpcList)
}
@ -495,22 +503,22 @@ class PreferencesController {
* @returns {Promise<array>} Promise resolving to updated frequentRpcList.
*
*/
addToFrequentRpcList (url, chainId, ticker = 'ETH', nickname = '') {
const rpcList = this.getFrequentRpcListDetail()
const index = rpcList.findIndex((element) => { return element.rpcUrl === url })
if (index !== -1) {
rpcList.splice(index, 1)
}
if (url !== 'http://localhost:8545') {
let checkedChainId
if (!!chainId && !Number.isNaN(parseInt(chainId))) {
checkedChainId = chainId
addToFrequentRpcList (url, chainId, ticker = 'ETH', nickname = '', rpcPrefs = {}) {
const rpcList = this.getFrequentRpcListDetail()
const index = rpcList.findIndex((element) => { return element.rpcUrl === url })
if (index !== -1) {
rpcList.splice(index, 1)
}
rpcList.push({ rpcUrl: url, chainId: checkedChainId, ticker, nickname })
if (url !== 'http://localhost:8545') {
let checkedChainId
if (!!chainId && !Number.isNaN(parseInt(chainId))) {
checkedChainId = chainId
}
rpcList.push({ rpcUrl: url, chainId: checkedChainId, ticker, nickname, rpcPrefs })
}
this.store.updateState({ frequentRpcListDetail: rpcList })
return Promise.resolve(rpcList)
}
this.store.updateState({ frequentRpcListDetail: rpcList })
return Promise.resolve(rpcList)
}
/**
* Removes custom RPC url from state.

@ -1,9 +1,11 @@
const ObservableStore = require('obs-store')
const SafeEventEmitter = require('safe-event-emitter')
const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware')
/**
* A controller that services user-approved requests for a full Ethereum provider API
*/
class ProviderApprovalController {
class ProviderApprovalController extends SafeEventEmitter {
/**
* Determines if caching is enabled
*/
@ -14,38 +16,44 @@ class ProviderApprovalController {
*
* @param {Object} [config] - Options to configure controller
*/
constructor ({ closePopup, keyringController, openPopup, platform, preferencesController, publicConfigStore } = {}) {
constructor ({ closePopup, keyringController, openPopup, preferencesController } = {}) {
super()
this.approvedOrigins = {}
this.closePopup = closePopup
this.keyringController = keyringController
this.openPopup = openPopup
this.platform = platform
this.preferencesController = preferencesController
this.publicConfigStore = publicConfigStore
this.store = new ObservableStore({
providerRequests: [],
})
}
if (platform && platform.addMessageListener) {
platform.addMessageListener(({ action = '', force, origin, siteTitle, siteImage }, { tab }) => {
if (tab && tab.id) {
switch (action) {
case 'init-provider-request':
this._handleProviderRequest(origin, siteTitle, siteImage, force, tab.id)
break
case 'init-is-approved':
this._handleIsApproved(origin, tab.id)
break
case 'init-is-unlocked':
this._handleIsUnlocked(tab.id)
break
case 'init-privacy-request':
this._handlePrivacyRequest(tab.id)
break
}
}
})
}
/**
* Called when a user approves access to a full Ethereum provider API
*
* @param {object} opts - opts for the middleware contains the origin for the middleware
*/
createMiddleware ({ origin, getSiteMetadata }) {
return createAsyncMiddleware(async (req, res, next) => {
// only handle requestAccounts
if (req.method !== 'eth_requestAccounts') return next()
// if already approved or privacy mode disabled, return early
const isUnlocked = this.keyringController.memStore.getState().isUnlocked
if (this.shouldExposeAccounts(origin) && isUnlocked) {
res.result = [this.preferencesController.getSelectedAddress()]
return
}
// register the provider request
const metadata = await getSiteMetadata(origin)
this._handleProviderRequest(origin, metadata.name, metadata.icon, false, null)
// wait for resolution of request
const approved = await new Promise(resolve => this.once(`resolvedRequest:${origin}`, ({ approved }) => resolve(approved)))
if (approved) {
res.result = [this.preferencesController.getSelectedAddress()]
} else {
throw new Error('User denied account authorization')
}
})
}
/**
@ -59,79 +67,37 @@ class ProviderApprovalController {
this.store.updateState({ providerRequests: [{ origin, siteTitle, siteImage, tabID }] })
const isUnlocked = this.keyringController.memStore.getState().isUnlocked
if (!force && this.approvedOrigins[origin] && this.caching && isUnlocked) {
this.approveProviderRequest(tabID)
return
}
this.openPopup && this.openPopup()
}
/**
* Called by a tab to determine if an origin has been approved in the past
*
* @param {string} origin - Origin of the window
*/
_handleIsApproved (origin, tabID) {
this.platform && this.platform.sendMessage({
action: 'answer-is-approved',
isApproved: this.approvedOrigins[origin] && this.caching,
caching: this.caching,
}, { id: tabID })
}
/**
* Called by a tab to determine if MetaMask is currently locked or unlocked
*/
_handleIsUnlocked (tabID) {
const isUnlocked = this.keyringController.memStore.getState().isUnlocked
this.platform && this.platform.sendMessage({ action: 'answer-is-unlocked', isUnlocked }, { id: tabID })
}
/**
* Called to check privacy mode; if privacy mode is off, this will automatically enable the provider (legacy behavior)
*/
_handlePrivacyRequest (tabID) {
const privacyMode = this.preferencesController.getFeatureFlags().privacyMode
if (!privacyMode) {
this.platform && this.platform.sendMessage({
action: 'approve-legacy-provider-request',
selectedAddress: this.publicConfigStore.getState().selectedAddress,
}, { id: tabID })
this.publicConfigStore.emit('update', this.publicConfigStore.getState())
}
}
/**
* Called when a user approves access to a full Ethereum provider API
*
* @param {string} tabID - ID of the target window that approved provider access
* @param {string} origin - origin of the domain that had provider access approved
*/
approveProviderRequest (tabID) {
approveProviderRequestByOrigin (origin) {
this.closePopup && this.closePopup()
const requests = this.store.getState().providerRequests
const origin = requests.find(request => request.tabID === tabID).origin
this.platform && this.platform.sendMessage({
action: 'approve-provider-request',
selectedAddress: this.publicConfigStore.getState().selectedAddress,
}, { id: tabID })
this.publicConfigStore.emit('update', this.publicConfigStore.getState())
const providerRequests = requests.filter(request => request.tabID !== tabID)
const providerRequests = requests.filter(request => request.origin !== origin)
this.store.updateState({ providerRequests })
this.approvedOrigins[origin] = true
this.emit(`resolvedRequest:${origin}`, { approved: true })
}
/**
* Called when a tab rejects access to a full Ethereum provider API
*
* @param {string} tabID - ID of the target window that rejected provider access
* @param {string} origin - origin of the domain that had provider access approved
*/
rejectProviderRequest (tabID) {
rejectProviderRequestByOrigin (origin) {
this.closePopup && this.closePopup()
const requests = this.store.getState().providerRequests
const origin = requests.find(request => request.tabID === tabID).origin
this.platform && this.platform.sendMessage({ action: 'reject-provider-request' }, { id: tabID })
const providerRequests = requests.filter(request => request.tabID !== tabID)
const providerRequests = requests.filter(request => request.origin !== origin)
this.store.updateState({ providerRequests })
delete this.approvedOrigins[origin]
this.emit(`resolvedRequest:${origin}`, { approved: false })
}
/**
@ -149,16 +115,10 @@ class ProviderApprovalController {
*/
shouldExposeAccounts (origin) {
const privacyMode = this.preferencesController.getFeatureFlags().privacyMode
return !privacyMode || this.approvedOrigins[origin]
const result = !privacyMode || Boolean(this.approvedOrigins[origin])
return result
}
/**
* Tells all tabs that MetaMask is now locked. This is primarily used to set
* internal flags in the contentscript and inpage script.
*/
setLocked () {
this.platform.sendMessage({ action: 'metamask-set-locked' })
}
}
module.exports = ProviderApprovalController

@ -8,8 +8,9 @@ const {
RINKEBY,
KOVAN,
MAINNET,
GOERLI,
} = require('./network/enums')
const INFURA_PROVIDER_TYPES = [ROPSTEN, RINKEBY, KOVAN, MAINNET]
const INFURA_PROVIDER_TYPES = [ROPSTEN, RINKEBY, KOVAN, MAINNET, GOERLI]
class RecentBlocksController {

@ -1,180 +0,0 @@
const ObservableStore = require('obs-store')
const extend = require('xtend')
const log = require('loglevel')
// every three seconds when an incomplete tx is waiting
const POLLING_INTERVAL = 3000
class ShapeshiftController {
/**
* Controller responsible for managing the list of shapeshift transactions. On construction, it initiates a poll
* that queries a shapeshift.io API for updates to any pending shapeshift transactions
*
* @typedef {Object} ShapeshiftController
* @param {object} opts Overrides the defaults for the initial state of this.store
* @property {array} opts.initState initializes the the state of the ShapeshiftController. Can contain an
* shapeShiftTxList array.
* @property {array} shapeShiftTxList An array of ShapeShiftTx objects
*
*/
constructor (opts = {}) {
const initState = extend({
shapeShiftTxList: [],
}, opts.initState)
this.store = new ObservableStore(initState)
this.pollForUpdates()
}
/**
* Represents, and contains data about, a single shapeshift transaction.
* @typedef {Object} ShapeShiftTx
* @property {string} depositAddress - An address at which to send a crypto deposit, so that eth can be sent to the
* user's Metamask account
* @property {string} depositType - An abbreviation of the type of crypto currency to be deposited.
* @property {string} key - The 'shapeshift' key differentiates this from other types of txs in Metamask
* @property {number} time - The time at which the tx was created
* @property {object} response - Initiated as an empty object, which will be replaced by a Response object. @see {@link
* https://developer.mozilla.org/en-US/docs/Web/API/Response}
*/
//
// PUBLIC METHODS
//
/**
* A getter for the shapeShiftTxList property
*
* @returns {array<ShapeShiftTx>}
*
*/
getShapeShiftTxList () {
const shapeShiftTxList = this.store.getState().shapeShiftTxList
return shapeShiftTxList
}
/**
* A getter for all ShapeShiftTx in the shapeShiftTxList that have not successfully completed a deposit.
*
* @returns {array<ShapeShiftTx>} Only includes ShapeShiftTx which has a response property with a status !== complete
*
*/
getPendingTxs () {
const txs = this.getShapeShiftTxList()
const pending = txs.filter(tx => tx.response && tx.response.status !== 'complete')
return pending
}
/**
* A poll that exists as long as there are pending transactions. Each call attempts to update the data of any
* pendingTxs, and then calls itself again. If there are no pending txs, the recursive call is not made and
* the polling stops.
*
* this.updateTx is used to attempt the update to the pendingTxs in the ShapeShiftTxList, and that updated data
* is saved with saveTx.
*
*/
pollForUpdates () {
const pendingTxs = this.getPendingTxs()
if (pendingTxs.length === 0) {
return
}
Promise.all(pendingTxs.map((tx) => {
return this.updateTx(tx)
}))
.then((results) => {
results.forEach(tx => this.saveTx(tx))
this.timeout = setTimeout(this.pollForUpdates.bind(this), POLLING_INTERVAL)
})
}
/**
* Attempts to update a ShapeShiftTx with data from a shapeshift.io API. Both the response and time properties
* can be updated. The response property is updated with every call, but the time property is only updated when
* the response status updates to 'complete'. This will occur once the user makes a deposit as the ShapeShiftTx
* depositAddress
*
* @param {ShapeShiftTx} tx The tx to update
*
*/
async updateTx (tx) {
try {
const url = `https://shapeshift.io/txStat/${tx.depositAddress}`
const response = await fetch(url)
const json = await response.json()
tx.response = json
if (tx.response.status === 'complete') {
tx.time = new Date().getTime()
}
return tx
} catch (err) {
log.warn(err)
}
}
/**
* Saves an updated to a ShapeShiftTx in the shapeShiftTxList. If the passed ShapeShiftTx is not in the
* shapeShiftTxList, nothing happens.
*
* @param {ShapeShiftTx} tx The updated tx to save, if it exists in the current shapeShiftTxList
*
*/
saveTx (tx) {
const { shapeShiftTxList } = this.store.getState()
const index = shapeShiftTxList.indexOf(tx)
if (index !== -1) {
shapeShiftTxList[index] = tx
this.store.updateState({ shapeShiftTxList })
}
}
/**
* Removes a ShapeShiftTx from the shapeShiftTxList
*
* @param {ShapeShiftTx} tx The tx to remove
*
*/
removeShapeShiftTx (tx) {
const { shapeShiftTxList } = this.store.getState()
const index = shapeShiftTxList.indexOf(index)
if (index !== -1) {
shapeShiftTxList.splice(index, 1)
}
this.updateState({ shapeShiftTxList })
}
/**
* Creates a new ShapeShiftTx, adds it to the shapeShiftTxList, and initiates a new poll for updates of pending txs
*
* @param {string} depositAddress - An address at which to send a crypto deposit, so that eth can be sent to the
* user's Metamask account
* @param {string} depositType - An abbreviation of the type of crypto currency to be deposited.
*
*/
createShapeShiftTx (depositAddress, depositType) {
const state = this.store.getState()
let { shapeShiftTxList } = state
var shapeShiftTx = {
depositAddress,
depositType,
key: 'shapeshift',
time: new Date().getTime(),
response: {},
}
if (!shapeShiftTxList) {
shapeShiftTxList = [shapeShiftTx]
} else {
shapeShiftTxList.push(shapeShiftTx)
}
this.store.updateState({ shapeShiftTxList })
this.pollForUpdates()
}
}
module.exports = ShapeshiftController

@ -1,6 +1,8 @@
const ObservableStore = require('obs-store')
const log = require('loglevel')
const normalizeAddress = require('eth-sig-util').normalize
const ethUtil = require('ethereumjs-util')
// By default, poll every 3 minutes
const DEFAULT_INTERVAL = 180 * 1000
@ -28,16 +30,16 @@ class TokenRatesController {
async updateExchangeRates () {
if (!this.isActive) { return }
const contractExchangeRates = {}
const nativeCurrency = this.currency ? this.currency.getState().nativeCurrency.toUpperCase() : 'ETH'
const pairs = this._tokens.map(token => `pairs[]=${token.address}/${nativeCurrency}`)
const query = pairs.join('&')
const nativeCurrency = this.currency ? this.currency.getState().nativeCurrency.toLowerCase() : 'eth'
const pairs = this._tokens.map(token => token.address).join(',')
const query = `contract_addresses=${pairs}&vs_currencies=${nativeCurrency}`
if (this._tokens.length > 0) {
try {
const response = await fetch(`https://exchanges.balanc3.net/pie?${query}&autoConversion=false`)
const { prices = [] } = await response.json()
prices.forEach(({ pair, price }) => {
const address = pair.split('/')[0]
contractExchangeRates[normalizeAddress(address)] = typeof price === 'number' ? price : 0
const response = await fetch(`https://api.coingecko.com/api/v3/simple/token_price/ethereum?${query}`)
const prices = await response.json()
this._tokens.forEach(token => {
const price = prices[token.address.toLowerCase()] || prices[ethUtil.toChecksumAddress(token.address)]
contractExchangeRates[normalizeAddress(token.address)] = price ? price[nativeCurrency] : 0
})
} catch (error) {
log.warn(`MetaMask - TokenRatesController exchange rate fetch failed.`, error)

@ -3,10 +3,21 @@ const ObservableStore = require('obs-store')
const ethUtil = require('ethereumjs-util')
const Transaction = require('ethereumjs-tx')
const EthQuery = require('ethjs-query')
const abi = require('human-standard-token-abi')
const abiDecoder = require('abi-decoder')
abiDecoder.addABI(abi)
const {
TOKEN_METHOD_APPROVE,
TOKEN_METHOD_TRANSFER,
TOKEN_METHOD_TRANSFER_FROM,
SEND_ETHER_ACTION_KEY,
DEPLOY_CONTRACT_ACTION_KEY,
CONTRACT_INTERACTION_KEY,
} = require('../../../../ui/app/helpers/constants/transactions.js')
const TransactionStateManager = require('./tx-state-manager')
const TxGasUtil = require('./tx-gas-utils')
const PendingTransactionTracker = require('./pending-tx-tracker')
const NonceTracker = require('./nonce-tracker')
const NonceTracker = require('nonce-tracker')
const txUtils = require('./lib/util')
const cleanErrorStack = require('../../lib/cleanErrorStack')
const log = require('loglevel')
@ -180,9 +191,11 @@ class TransactionController extends EventEmitter {
}
txUtils.validateTxParams(normalizedTxParams)
// construct txMeta
const { transactionCategory, getCodeResponse } = await this._determineTransactionCategory(txParams)
let txMeta = this.txStateManager.generateTxMeta({
txParams: normalizedTxParams,
type: TRANSACTION_TYPE_STANDARD,
transactionCategory,
})
this.addTx(txMeta)
this.emit('newUnapprovedTx', txMeta)
@ -191,7 +204,7 @@ class TransactionController extends EventEmitter {
// check whether recipient account is blacklisted
recipientBlacklistChecker.checkAccount(txMeta.metamaskNetworkId, normalizedTxParams.to)
// add default tx params
txMeta = await this.addTxGasDefaults(txMeta)
txMeta = await this.addTxGasDefaults(txMeta, getCodeResponse)
} catch (error) {
log.warn(error)
txMeta.loadingDefaults = false
@ -211,7 +224,7 @@ class TransactionController extends EventEmitter {
@param txMeta {Object} - the txMeta object
@returns {Promise<object>} resolves with txMeta
*/
async addTxGasDefaults (txMeta) {
async addTxGasDefaults (txMeta, getCodeResponse) {
const txParams = txMeta.txParams
// ensure value
txParams.value = txParams.value ? ethUtil.addHexPrefix(txParams.value) : '0x0'
@ -222,7 +235,7 @@ class TransactionController extends EventEmitter {
}
txParams.gasPrice = ethUtil.addHexPrefix(gasPrice.toString(16))
// set gasLimit
return await this.txGasUtil.analyzeGasUsage(txMeta)
return await this.txGasUtil.analyzeGasUsage(txMeta, getCodeResponse)
}
/**
@ -542,6 +555,7 @@ class TransactionController extends EventEmitter {
})
this.pendingTxTracker.on('tx:failed', this.txStateManager.setTxStatusFailed.bind(this.txStateManager))
this.pendingTxTracker.on('tx:confirmed', (txId) => this.confirmTransaction(txId))
this.pendingTxTracker.on('tx:dropped', this.txStateManager.setTxStatusDropped.bind(this.txStateManager))
this.pendingTxTracker.on('tx:block-update', (txMeta, latestBlockNumber) => {
if (!txMeta.firstRetryBlockNumber) {
txMeta.firstRetryBlockNumber = latestBlockNumber
@ -555,6 +569,43 @@ class TransactionController extends EventEmitter {
})
}
/**
Returns a "type" for a transaction out of the following list: simpleSend, tokenTransfer, tokenApprove,
contractDeployment, contractMethodCall
*/
async _determineTransactionCategory (txParams) {
const { data, to } = txParams
const { name } = data && abiDecoder.decodeMethod(data) || {}
const tokenMethodName = [
TOKEN_METHOD_APPROVE,
TOKEN_METHOD_TRANSFER,
TOKEN_METHOD_TRANSFER_FROM,
].find(tokenMethodName => tokenMethodName === name && name.toLowerCase())
let result
let code
if (!txParams.data) {
result = SEND_ETHER_ACTION_KEY
} else if (tokenMethodName) {
result = tokenMethodName
} else if (!to) {
result = DEPLOY_CONTRACT_ACTION_KEY
} else {
try {
code = await this.query.getCode(to)
} catch (e) {
code = null
log.warn(e)
}
// For an address with no code, geth will return '0x', and ganache-core v2.2.1 will return '0x0'
const codeIsEmpty = !code || code === '0x' || code === '0x0'
result = codeIsEmpty ? SEND_ETHER_ACTION_KEY : CONTRACT_INTERACTION_KEY
}
return { transactionCategory: result, getCodeResponse: code }
}
/**
Sets other txMeta statuses to dropped if the txMeta that has been confirmed has other transactions
in the list have the same nonce

@ -1,161 +0,0 @@
const EthQuery = require('ethjs-query')
const assert = require('assert')
const Mutex = require('await-semaphore').Mutex
/**
@param opts {Object}
@param {Object} opts.provider a ethereum provider
@param {Function} opts.getPendingTransactions a function that returns an array of txMeta
whosee status is `submitted`
@param {Function} opts.getConfirmedTransactions a function that returns an array of txMeta
whose status is `confirmed`
@class
*/
class NonceTracker {
constructor ({ provider, blockTracker, getPendingTransactions, getConfirmedTransactions }) {
this.provider = provider
this.blockTracker = blockTracker
this.ethQuery = new EthQuery(provider)
this.getPendingTransactions = getPendingTransactions
this.getConfirmedTransactions = getConfirmedTransactions
this.lockMap = {}
}
/**
@returns {Promise<Object>} with the key releaseLock (the gloabl mutex)
*/
async getGlobalLock () {
const globalMutex = this._lookupMutex('global')
// await global mutex free
const releaseLock = await globalMutex.acquire()
return { releaseLock }
}
/**
* @typedef NonceDetails
* @property {number} highestLocallyConfirmed - A hex string of the highest nonce on a confirmed transaction.
* @property {number} nextNetworkNonce - The next nonce suggested by the eth_getTransactionCount method.
* @property {number} highestSuggested - The maximum between the other two, the number returned.
*/
/**
this will return an object with the `nextNonce` `nonceDetails` of type NonceDetails, and the releaseLock
Note: releaseLock must be called after adding a signed tx to pending transactions (or discarding).
@param address {string} the hex string for the address whose nonce we are calculating
@returns {Promise<NonceDetails>}
*/
async getNonceLock (address) {
// await global mutex free
await this._globalMutexFree()
// await lock free, then take lock
const releaseLock = await this._takeMutex(address)
try {
// evaluate multiple nextNonce strategies
const nonceDetails = {}
const networkNonceResult = await this._getNetworkNextNonce(address)
const highestLocallyConfirmed = this._getHighestLocallyConfirmed(address)
const nextNetworkNonce = networkNonceResult.nonce
const highestSuggested = Math.max(nextNetworkNonce, highestLocallyConfirmed)
const pendingTxs = this.getPendingTransactions(address)
const localNonceResult = this._getHighestContinuousFrom(pendingTxs, highestSuggested) || 0
nonceDetails.params = {
highestLocallyConfirmed,
highestSuggested,
nextNetworkNonce,
}
nonceDetails.local = localNonceResult
nonceDetails.network = networkNonceResult
const nextNonce = Math.max(networkNonceResult.nonce, localNonceResult.nonce)
assert(Number.isInteger(nextNonce), `nonce-tracker - nextNonce is not an integer - got: (${typeof nextNonce}) "${nextNonce}"`)
// return nonce and release cb
return { nextNonce, nonceDetails, releaseLock }
} catch (err) {
// release lock if we encounter an error
releaseLock()
throw err
}
}
async _globalMutexFree () {
const globalMutex = this._lookupMutex('global')
const releaseLock = await globalMutex.acquire()
releaseLock()
}
async _takeMutex (lockId) {
const mutex = this._lookupMutex(lockId)
const releaseLock = await mutex.acquire()
return releaseLock
}
_lookupMutex (lockId) {
let mutex = this.lockMap[lockId]
if (!mutex) {
mutex = new Mutex()
this.lockMap[lockId] = mutex
}
return mutex
}
async _getNetworkNextNonce (address) {
// calculate next nonce
// we need to make sure our base count
// and pending count are from the same block
const blockNumber = await this.blockTracker.getLatestBlock()
const baseCountBN = await this.ethQuery.getTransactionCount(address, blockNumber)
const baseCount = baseCountBN.toNumber()
assert(Number.isInteger(baseCount), `nonce-tracker - baseCount is not an integer - got: (${typeof baseCount}) "${baseCount}"`)
const nonceDetails = { blockNumber, baseCount }
return { name: 'network', nonce: baseCount, details: nonceDetails }
}
_getHighestLocallyConfirmed (address) {
const confirmedTransactions = this.getConfirmedTransactions(address)
const highest = this._getHighestNonce(confirmedTransactions)
return Number.isInteger(highest) ? highest + 1 : 0
}
_getHighestNonce (txList) {
const nonces = txList.map((txMeta) => {
const nonce = txMeta.txParams.nonce
assert(typeof nonce, 'string', 'nonces should be hex strings')
return parseInt(nonce, 16)
})
const highestNonce = Math.max.apply(null, nonces)
return highestNonce
}
/**
@typedef {object} highestContinuousFrom
@property {string} - name the name for how the nonce was calculated based on the data used
@property {number} - nonce the next suggested nonce
@property {object} - details the provided starting nonce that was used (for debugging)
*/
/**
@param txList {array} - list of txMeta's
@param startPoint {number} - the highest known locally confirmed nonce
@returns {highestContinuousFrom}
*/
_getHighestContinuousFrom (txList, startPoint) {
const nonces = txList.map((txMeta) => {
const nonce = txMeta.txParams.nonce
assert(typeof nonce, 'string', 'nonces should be hex strings')
return parseInt(nonce, 16)
})
let highest = startPoint
while (nonces.includes(highest)) {
highest++
}
return { name: 'local', nonce: highest, details: { startPoint, highest } }
}
}
module.exports = NonceTracker

@ -22,6 +22,7 @@ const EthQuery = require('ethjs-query')
class PendingTransactionTracker extends EventEmitter {
constructor (config) {
super()
this.droppedBuffer = {}
this.query = new EthQuery(config.provider)
this.nonceTracker = config.nonceTracker
this.getPendingTransactions = config.getPendingTransactions
@ -139,22 +140,42 @@ class PendingTransactionTracker extends EventEmitter {
const noTxHashErr = new Error('We had an error while submitting this transaction, please try again.')
noTxHashErr.name = 'NoTxHashError'
this.emit('tx:failed', txId, noTxHashErr)
return
}
// If another tx with the same nonce is mined, set as failed.
// If another tx with the same nonce is mined, set as dropped.
const taken = await this._checkIfNonceIsTaken(txMeta)
if (taken) {
const nonceTakenErr = new Error('Another transaction with this nonce has been mined.')
nonceTakenErr.name = 'NonceTakenErr'
return this.emit('tx:failed', txId, nonceTakenErr)
let dropped
try {
// check the network if the nonce is ahead the tx
// and the tx has not been mined into a block
dropped = await this._checkIftxWasDropped(txMeta)
// the dropped buffer is in case we ask a node for the tx
// that is behind the node we asked for tx count
// IS A SECURITY FOR HITTING NODES IN INFURA THAT COULD GO OUT
// OF SYNC.
// on the next block event it will return fire as dropped
if (dropped && !this.droppedBuffer[txHash]) {
this.droppedBuffer[txHash] = true
dropped = false
} else if (dropped && this.droppedBuffer[txHash]) {
// clean up
delete this.droppedBuffer[txHash]
}
} catch (e) {
log.error(e)
}
if (taken || dropped) {
return this.emit('tx:dropped', txId)
}
// get latest transaction status
try {
const txParams = await this.query.getTransactionByHash(txHash)
if (!txParams) return
if (txParams.blockNumber) {
const { blockNumber } = await this.query.getTransactionByHash(txHash) || {}
if (blockNumber) {
this.emit('tx:confirmed', txId)
}
} catch (err) {
@ -165,6 +186,22 @@ class PendingTransactionTracker extends EventEmitter {
this.emit('tx:warning', txMeta, err)
}
}
/**
checks to see if if the tx's nonce has been used by another transaction
@param txMeta {Object} - txMeta object
@emits tx:dropped
@returns {boolean}
*/
async _checkIftxWasDropped (txMeta) {
const { txParams: { nonce, from }, hash } = txMeta
const nextNonce = await this.query.getTransactionCount(from)
const { blockNumber } = await this.query.getTransactionByHash(hash) || {}
if (!blockNumber && parseInt(nextNonce) > parseInt(nonce)) {
return true
}
return false
}
/**
checks to see if a confirmed txMeta has the same nonce

@ -4,7 +4,9 @@ const {
BnMultiplyByFraction,
bnToHex,
} = require('../../lib/util')
const log = require('loglevel')
const { addHexPrefix } = require('ethereumjs-util')
const { SEND_ETHER_ACTION_KEY } = require('../../../../ui/app/helpers/constants/transactions.js')
const SIMPLE_GAS_COST = '0x5208' // Hex for 21000, cost of a simple send.
import { TRANSACTION_NO_CONTRACT_ERROR_KEY } from '../../../../ui/app/helpers/constants/error-keys'
@ -26,12 +28,13 @@ class TxGasUtil {
@param txMeta {Object} - the txMeta object
@returns {object} the txMeta object with the gas written to the txParams
*/
async analyzeGasUsage (txMeta) {
async analyzeGasUsage (txMeta, getCodeResponse) {
const block = await this.query.getBlockByNumber('latest', false)
let estimatedGasHex
try {
estimatedGasHex = await this.estimateTxGas(txMeta, block.gasLimit)
estimatedGasHex = await this.estimateTxGas(txMeta, block.gasLimit, getCodeResponse)
} catch (err) {
log.warn(err)
txMeta.simulationFails = {
reason: err.message,
errorKey: err.errorKey,
@ -54,7 +57,7 @@ class TxGasUtil {
@param blockGasLimitHex {string} - hex string of the block's gas limit
@returns {string} the estimated gas limit as a hex string
*/
async estimateTxGas (txMeta, blockGasLimitHex) {
async estimateTxGas (txMeta, blockGasLimitHex, getCodeResponse) {
const txParams = txMeta.txParams
// check if gasLimit is already specified
@ -70,11 +73,10 @@ class TxGasUtil {
// see if we can set the gas based on the recipient
if (hasRecipient) {
const code = await this.query.getCode(recipient)
// For an address with no code, geth will return '0x', and ganache-core v2.2.1 will return '0x0'
const codeIsEmpty = !code || code === '0x' || code === '0x0'
const categorizedAsSimple = txMeta.transactionCategory === SEND_ETHER_ACTION_KEY
if (codeIsEmpty) {
if (categorizedAsSimple) {
// if there's data in the params, but there's no contract code, it's not a valid transaction
if (txParams.data) {
const err = new Error('TxGasUtil - Trying to call a function on a non-contract address')
@ -82,7 +84,7 @@ class TxGasUtil {
err.errorKey = TRANSACTION_NO_CONTRACT_ERROR_KEY
// set the response on the error so that we can see in logs what the actual response was
err.getCodeResponse = code
err.getCodeResponse = getCodeResponse
throw err
}

@ -126,10 +126,10 @@ class TransactionStateManager extends EventEmitter {
@returns {object} the txMeta
*/
addTx (txMeta) {
this.once(`${txMeta.id}:signed`, function (txId) {
this.once(`${txMeta.id}:signed`, function () {
this.removeAllListeners(`${txMeta.id}:rejected`)
})
this.once(`${txMeta.id}:rejected`, function (txId) {
this.once(`${txMeta.id}:rejected`, function () {
this.removeAllListeners(`${txMeta.id}:signed`)
})
// initialize history

@ -1,17 +0,0 @@
const MessageManager = require('./lib/message-manager')
const PersonalMessageManager = require('./lib/personal-message-manager')
const TypedMessageManager = require('./lib/typed-message-manager')
class UserActionController {
constructor (opts = {}) {
this.messageManager = new MessageManager()
this.personalMessageManager = new PersonalMessageManager()
this.typedMessageManager = new TypedMessageManager()
}
}
module.exports = UserActionController

@ -4,18 +4,10 @@ class StandardProvider {
constructor (provider) {
this._provider = provider
this._onMessage('ethereumpingerror', this._onClose.bind(this))
this._onMessage('ethereumpingsuccess', this._onConnect.bind(this))
window.addEventListener('load', () => {
this._subscribe()
this._ping()
})
}
_onMessage (type, handler) {
window.addEventListener('message', function ({ data }) {
if (!data || data.type !== type) return
handler.apply(this, arguments)
this._subscribe()
// indicate that we've connected, mostly just for standard compliance
setTimeout(() => {
this._onConnect()
})
}
@ -34,15 +26,6 @@ class StandardProvider {
this._isConnected = true
}
async _ping () {
try {
await this.send('net_version')
window.postMessage({ type: 'ethereumpingsuccess' }, '*')
} catch (error) {
window.postMessage({ type: 'ethereumpingerror' }, '*')
}
}
_subscribe () {
this._provider.on('data', (error, { method, params }) => {
if (!error && method === 'eth_subscription') {
@ -59,11 +42,9 @@ class StandardProvider {
* @returns {Promise<*>} Promise resolving to the result if successful
*/
send (method, params = []) {
if (method === 'eth_requestAccounts') return this._provider.enable()
return new Promise((resolve, reject) => {
try {
this._provider.sendAsync({ method, params, beta: true }, (error, response) => {
this._provider.sendAsync({ id: 1, jsonrpc: '2.0', method, params }, (error, response) => {
error = error || response.error
error ? reject(error) : resolve(response)
})

@ -7,32 +7,12 @@ const setupDappAutoReload = require('./lib/auto-reload.js')
const MetamaskInpageProvider = require('metamask-inpage-provider')
const createStandardProvider = require('./createStandardProvider').default
let isEnabled = false
let warned = false
let providerHandle
let isApprovedHandle
let isUnlockedHandle
restoreContextAfterImports()
log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn')
/**
* Adds a postMessage listener for a specific message type
*
* @param {string} messageType - postMessage type to listen for
* @param {Function} handler - event handler
* @param {boolean} remove - removes this handler after being triggered
*/
function onMessage (messageType, callback, remove) {
const handler = function ({ data }) {
if (!data || data.type !== messageType) { return }
remove && window.removeEventListener('message', handler)
callback.apply(window, arguments)
}
window.addEventListener('message', handler)
}
//
// setup plugin communication
//
@ -49,45 +29,16 @@ const inpageProvider = new MetamaskInpageProvider(metamaskStream)
// set a high max listener count to avoid unnecesary warnings
inpageProvider.setMaxListeners(100)
// set up a listener for when MetaMask is locked
onMessage('metamasksetlocked', () => { isEnabled = false })
// set up a listener for privacy mode responses
onMessage('ethereumproviderlegacy', ({ data: { selectedAddress } }) => {
isEnabled = true
setTimeout(() => {
inpageProvider.publicConfigStore.updateState({ selectedAddress })
}, 0)
}, true)
// augment the provider with its enable method
inpageProvider.enable = function ({ force } = {}) {
return new Promise((resolve, reject) => {
providerHandle = ({ data: { error, selectedAddress } }) => {
if (typeof error !== 'undefined') {
reject({
message: error,
code: 4001,
})
inpageProvider.sendAsync({ method: 'eth_requestAccounts', params: [force] }, (error, response) => {
if (error) {
reject(error)
} else {
window.removeEventListener('message', providerHandle)
setTimeout(() => {
inpageProvider.publicConfigStore.updateState({ selectedAddress })
}, 0)
// wait for the background to update with an account
inpageProvider.sendAsync({ method: 'eth_accounts', params: [] }, (error, response) => {
if (error) {
reject(error)
} else {
isEnabled = true
resolve(response.result)
}
})
resolve(response.result)
}
}
onMessage('ethereumprovider', providerHandle, true)
window.postMessage({ type: 'ETHEREUM_ENABLE_PROVIDER', force }, '*')
})
})
}
@ -98,31 +49,23 @@ inpageProvider.autoRefreshOnNetworkChange = true
// add metamask-specific convenience methods
inpageProvider._metamask = new Proxy({
/**
* Determines if this domain is currently enabled
* Synchronously determines if this domain is currently enabled, with a potential false negative if called to soon
*
* @returns {boolean} - true if this domain is currently enabled
* @returns {boolean} - returns true if this domain is currently enabled
*/
isEnabled: function () {
return isEnabled
const { isEnabled } = inpageProvider.publicConfigStore.getState()
return Boolean(isEnabled)
},
/**
* Determines if this domain has been previously approved
* Asynchronously determines if this domain is currently enabled
*
* @returns {Promise<boolean>} - Promise resolving to true if this domain has been previously approved
* @returns {Promise<boolean>} - Promise resolving to true if this domain is currently enabled
*/
isApproved: function () {
return new Promise((resolve) => {
isApprovedHandle = ({ data: { caching, isApproved } }) => {
if (caching) {
resolve(!!isApproved)
} else {
resolve(false)
}
}
onMessage('ethereumisapproved', isApprovedHandle, true)
window.postMessage({ type: 'ETHEREUM_IS_APPROVED' }, '*')
})
isApproved: async function () {
const { isEnabled } = await getPublicConfigWhenReady()
return Boolean(isEnabled)
},
/**
@ -130,14 +73,9 @@ inpageProvider._metamask = new Proxy({
*
* @returns {Promise<boolean>} - Promise resolving to true if MetaMask is currently unlocked
*/
isUnlocked: function () {
return new Promise((resolve) => {
isUnlockedHandle = ({ data: { isUnlocked } }) => {
resolve(!!isUnlocked)
}
onMessage('metamaskisunlocked', isUnlockedHandle, true)
window.postMessage({ type: 'METAMASK_IS_UNLOCKED' }, '*')
})
isUnlocked: async function () {
const { isUnlocked } = await getPublicConfigWhenReady()
return Boolean(isUnlocked)
},
}, {
get: function (obj, prop) {
@ -149,6 +87,19 @@ inpageProvider._metamask = new Proxy({
},
})
// publicConfig isn't populated until we get a message from background.
// Using this getter will ensure the state is available
async function getPublicConfigWhenReady () {
const store = inpageProvider.publicConfigStore
let state = store.getState()
// if state is missing, wait for first update
if (!state.networkVersion) {
state = await new Promise(resolve => store.once('update', resolve))
console.log('new state', state)
}
return state
}
// Work around for web3@1.0 deleting the bound `sendAsync` but not the unbound
// `sendAsync` method on the prototype, causing `this` reference issues with drizzle
const proxiedInpageProvider = new Proxy(inpageProvider, {
@ -159,19 +110,6 @@ const proxiedInpageProvider = new Proxy(inpageProvider, {
window.ethereum = createStandardProvider(proxiedInpageProvider)
// detect eth_requestAccounts and pipe to enable for now
function detectAccountRequest (method) {
const originalMethod = inpageProvider[method]
inpageProvider[method] = function ({ method }) {
if (method === 'eth_requestAccounts') {
return window.ethereum.enable()
}
return originalMethod.apply(this, arguments)
}
}
detectAccountRequest('send')
detectAccountRequest('sendAsync')
//
// setup web3
//
@ -218,6 +156,12 @@ inpageProvider.publicConfigStore.subscribe(function (state) {
web3.eth.defaultAccount = state.selectedAddress
})
inpageProvider.publicConfigStore.subscribe(function (state) {
if (state.onboardingcomplete) {
window.postMessage('onboardingcomplete', '*')
}
})
// need to make sure we aren't affected by overlapping namespaces
// and that we dont affect the app with our namespace
// mostly a fix for web3's BigNumber if AMD's "define" is defined...

@ -0,0 +1,26 @@
const {
getMetaMetricState,
} = require('../../../ui/app/selectors/selectors')
const {
sendMetaMetricsEvent,
} = require('../../../ui/app/helpers/utils/metametrics.util')
const inDevelopment = process.env.NODE_ENV === 'development'
const METAMETRICS_TRACKING_URL = inDevelopment
? 'http://www.metamask.io/metametrics'
: 'http://www.metamask.io/metametrics-prod'
function backEndMetaMetricsEvent (metaMaskState, eventData) {
const stateEventData = getMetaMetricState({ metamask: metaMaskState })
if (stateEventData.participateInMetaMetrics) {
sendMetaMetricsEvent({
...stateEventData,
...eventData,
url: METAMETRICS_TRACKING_URL + '/backend',
})
}
}
module.exports = backEndMetaMetricsEvent

@ -28,6 +28,8 @@ function getBuyEthUrl ({ network, amount, address, service }) {
return 'https://www.rinkeby.io/'
case 'kovan-faucet':
return 'https://github.com/kovan-testnet/faucet'
case 'goerli-faucet':
return 'https://goerli-faucet.slock.it/'
}
throw new Error(`Unknown cryptocurrency exchange or faucet: "${service}"`)
}
@ -42,6 +44,8 @@ function getDefaultServiceForNetwork (network) {
return 'rinkeby-faucet'
case '42':
return 'kovan-faucet'
case '5':
return 'goerli-faucet'
}
throw new Error(`No default cryptocurrency exchange or faucet for networkId: "${network}"`)
}

@ -0,0 +1,16 @@
module.exports = createDnodeRemoteGetter
function createDnodeRemoteGetter (dnode) {
let remote
dnode.once('remote', (_remote) => {
remote = _remote
})
async function getRemote () {
if (remote) return remote
return await new Promise(resolve => dnode.once('remote', resolve))
}
return getRemote
}

@ -1,16 +0,0 @@
module.exports = createProviderMiddleware
/**
* Forwards an HTTP request to the current Web3 provider
*
* @param {{ provider: Object }} config Configuration containing current Web3 provider
*/
function createProviderMiddleware ({ provider }) {
return (req, res, next, end) => {
provider.sendAsync(req, (err, _res) => {
if (err) return end(err)
res.result = _res.result
end()
})
}
}

@ -34,7 +34,7 @@ module.exports = class MessageManager extends EventEmitter {
* @property {array} messages Holds all messages that have been created by this MessageManager
*
*/
constructor (opts) {
constructor () {
super()
this.memStore = new ObservableStore({
unapprovedMsgs: {},

@ -36,7 +36,7 @@ module.exports = class PersonalMessageManager extends EventEmitter {
* @property {array} messages Holds all messages that have been created by this PersonalMessageManager
*
*/
constructor (opts) {
constructor () {
super()
this.memStore = new ObservableStore({
unapprovedPersonalMsgs: {},

@ -7,8 +7,10 @@
const EventEmitter = require('events')
const pump = require('pump')
const Dnode = require('dnode')
const pify = require('pify')
const ObservableStore = require('obs-store')
const ComposableObservableStore = require('./lib/ComposableObservableStore')
const createDnodeRemoteGetter = require('./lib/createDnodeRemoteGetter')
const asStream = require('obs-store/lib/asStream')
const AccountTracker = require('./lib/account-tracker')
const RpcEngine = require('json-rpc-engine')
@ -18,15 +20,14 @@ const createFilterMiddleware = require('eth-json-rpc-filters')
const createSubscriptionManager = require('eth-json-rpc-filters/subscriptionManager')
const createOriginMiddleware = require('./lib/createOriginMiddleware')
const createLoggerMiddleware = require('./lib/createLoggerMiddleware')
const createProviderMiddleware = require('./lib/createProviderMiddleware')
const providerAsMiddleware = require('eth-json-rpc-middleware/providerAsMiddleware')
const {setupMultiplex} = require('./lib/stream-utils.js')
const KeyringController = require('eth-keyring-controller')
const NetworkController = require('./controllers/network')
const PreferencesController = require('./controllers/preferences')
const AppStateController = require('./controllers/app-state')
const CurrencyController = require('./controllers/currency')
const ShapeShiftController = require('./controllers/shapeshift')
const InfuraController = require('./controllers/infura')
const BlacklistController = require('./controllers/blacklist')
const CachedBalancesController = require('./controllers/cached-balances')
const RecentBlocksController = require('./controllers/recent-blocks')
const MessageManager = require('./lib/message-manager')
@ -53,7 +54,12 @@ const HW_WALLETS_KEYRINGS = [TrezorKeyring.type, LedgerBridgeKeyring.type]
const EthQuery = require('eth-query')
const ethUtil = require('ethereumjs-util')
const sigUtil = require('eth-sig-util')
const { AddressBookController } = require('gaba')
const {
AddressBookController,
ShapeShiftController,
PhishingController,
} = require('gaba')
const backEndMetaMetricsEvent = require('./lib/backend-metametrics')
module.exports = class MetamaskController extends EventEmitter {
@ -86,7 +92,7 @@ module.exports = class MetamaskController extends EventEmitter {
this.createVaultMutex = new Mutex()
// network store
this.networkController = new NetworkController(initState.NetworkController, this.platform)
this.networkController = new NetworkController(initState.NetworkController)
// preferences controller
this.preferencesController = new PreferencesController({
@ -96,6 +102,12 @@ module.exports = class MetamaskController extends EventEmitter {
network: this.networkController,
})
// app-state controller
this.appStateController = new AppStateController({
preferencesStore: this.preferencesController.store,
onInactiveTimeout: () => this.setLocked(),
})
// currency controller
this.currencyController = new CurrencyController({
initState: initState.CurrencyController,
@ -109,8 +121,7 @@ module.exports = class MetamaskController extends EventEmitter {
})
this.infuraController.scheduleInfuraNetworkCheck()
this.blacklistController = new BlacklistController()
this.blacklistController.scheduleUpdates()
this.phishingController = new PhishingController()
// rpc provider
this.initializeProvider()
@ -190,10 +201,26 @@ module.exports = class MetamaskController extends EventEmitter {
})
this.txController.on('newUnapprovedTx', () => opts.showUnapprovedTx())
this.txController.on(`tx:status-update`, (txId, status) => {
this.txController.on(`tx:status-update`, async (txId, status) => {
if (status === 'confirmed' || status === 'failed') {
const txMeta = this.txController.txStateManager.getTx(txId)
this.platform.showTransactionNotification(txMeta)
const { txReceipt } = txMeta
const participateInMetaMetrics = this.preferencesController.getParticipateInMetaMetrics()
if (txReceipt && txReceipt.status === '0x0' && participateInMetaMetrics) {
const metamaskState = await this.getState()
backEndMetaMetricsEvent(metamaskState, {
customVariables: {
errorMessage: txMeta.simulationFails.reason,
},
eventOpts: {
category: 'backend',
action: 'Transactions',
name: 'On Chain Failure',
},
})
}
}
})
@ -210,38 +237,40 @@ module.exports = class MetamaskController extends EventEmitter {
})
this.balancesController.updateAllBalances()
this.shapeshiftController = new ShapeShiftController({
initState: initState.ShapeShiftController,
})
this.shapeshiftController = new ShapeShiftController(undefined, initState.ShapeShiftController)
this.networkController.lookupNetwork()
this.messageManager = new MessageManager()
this.personalMessageManager = new PersonalMessageManager()
this.typedMessageManager = new TypedMessageManager({ networkController: this.networkController })
this.publicConfigStore = this.initPublicConfigStore()
// ensure isClientOpenAndUnlocked is updated when memState updates
this.on('update', (memState) => {
this.isClientOpenAndUnlocked = memState.isUnlocked && this._isClientOpen
})
this.providerApprovalController = new ProviderApprovalController({
closePopup: opts.closePopup,
keyringController: this.keyringController,
openPopup: opts.openPopup,
platform: opts.platform,
preferencesController: this.preferencesController,
publicConfigStore: this.publicConfigStore,
})
this.store.updateStructure({
AppStateController: this.appStateController.store,
TransactionController: this.txController.store,
KeyringController: this.keyringController.store,
PreferencesController: this.preferencesController.store,
AddressBookController: this.addressBookController,
CurrencyController: this.currencyController.store,
ShapeShiftController: this.shapeshiftController.store,
ShapeShiftController: this.shapeshiftController,
NetworkController: this.networkController.store,
InfuraController: this.infuraController.store,
CachedBalancesController: this.cachedBalancesController.store,
})
this.memStore = new ComposableObservableStore(null, {
AppStateController: this.appStateController.store,
NetworkController: this.networkController.store,
AccountTracker: this.accountTracker.store,
TxController: this.txController.memStore,
@ -256,7 +285,7 @@ module.exports = class MetamaskController extends EventEmitter {
RecentBlocksController: this.recentBlocksController.store,
AddressBookController: this.addressBookController,
CurrencyController: this.currencyController.store,
ShapeshiftController: this.shapeshiftController.store,
ShapeshiftController: this.shapeshiftController,
InfuraController: this.infuraController.store,
ProviderApprovalController: this.providerApprovalController.store,
})
@ -305,21 +334,32 @@ module.exports = class MetamaskController extends EventEmitter {
* Constructor helper: initialize a public config store.
* This store is used to make some config info available to Dapps synchronously.
*/
initPublicConfigStore () {
// get init state
createPublicConfigStore ({ checkIsEnabled }) {
// subset of state for metamask inpage provider
const publicConfigStore = new ObservableStore()
// memStore -> transform -> publicConfigStore
this.on('update', (memState) => {
this.isClientOpenAndUnlocked = memState.isUnlocked && this._isClientOpen
// setup memStore subscription hooks
this.on('update', updatePublicConfigStore)
updatePublicConfigStore(this.getState())
publicConfigStore.destroy = () => {
this.removeEventListener && this.removeEventListener('update', updatePublicConfigStore)
}
function updatePublicConfigStore (memState) {
const publicState = selectPublicState(memState)
publicConfigStore.putState(publicState)
})
}
function selectPublicState (memState) {
function selectPublicState ({ isUnlocked, selectedAddress, network, completedOnboarding }) {
const isEnabled = checkIsEnabled()
const isReady = isUnlocked && isEnabled
const result = {
selectedAddress: memState.isUnlocked ? memState.selectedAddress : undefined,
networkVersion: memState.network,
isUnlocked,
isEnabled,
selectedAddress: isReady ? selectedAddress : undefined,
networkVersion: network,
onboardingcomplete: completedOnboarding,
}
return result
}
@ -429,6 +469,9 @@ module.exports = class MetamaskController extends EventEmitter {
// AddressController
setAddressBook: this.addressBookController.set.bind(this.addressBookController),
// AppStateController
setLastActiveTime: nodeify(this.appStateController.setLastActiveTime, this.appStateController),
// KeyringController
setLocked: nodeify(this.setLocked, this),
createNewVaultAndKeychain: nodeify(this.createNewVaultAndKeychain, this),
@ -459,9 +502,10 @@ module.exports = class MetamaskController extends EventEmitter {
signTypedMessage: nodeify(this.signTypedMessage, this),
cancelTypedMessage: this.cancelTypedMessage.bind(this),
approveProviderRequest: providerApprovalController.approveProviderRequest.bind(providerApprovalController),
// provider approval
approveProviderRequestByOrigin: providerApprovalController.approveProviderRequestByOrigin.bind(providerApprovalController),
rejectProviderRequestByOrigin: providerApprovalController.rejectProviderRequestByOrigin.bind(providerApprovalController),
clearApprovedOrigins: providerApprovalController.clearApprovedOrigins.bind(providerApprovalController),
rejectProviderRequest: providerApprovalController.rejectProviderRequest.bind(providerApprovalController),
}
}
@ -1189,9 +1233,8 @@ module.exports = class MetamaskController extends EventEmitter {
* with higher gas.
*
* @param {string} txId - The ID of the transaction to speed up.
* @param {Function} cb - The callback function called with a full state update.
*/
async retryTransaction (txId, gasPrice, cb) {
async retryTransaction (txId, gasPrice) {
await this.txController.retryTransaction(txId, gasPrice)
const state = await this.getState()
return state
@ -1204,7 +1247,7 @@ module.exports = class MetamaskController extends EventEmitter {
* @param {string=} customGasPrice - the hex value to use for the cancel transaction
* @returns {object} MetaMask state
*/
async createCancelTransaction (originalTxId, customGasPrice, cb) {
async createCancelTransaction (originalTxId, customGasPrice) {
try {
await this.txController.createCancelTransaction(originalTxId, customGasPrice)
const state = await this.getState()
@ -1214,7 +1257,7 @@ module.exports = class MetamaskController extends EventEmitter {
}
}
async createSpeedUpTransaction (originalTxId, customGasPrice, cb) {
async createSpeedUpTransaction (originalTxId, customGasPrice) {
await this.txController.createSpeedUpTransaction(originalTxId, customGasPrice)
const state = await this.getState()
return state
@ -1269,7 +1312,7 @@ module.exports = class MetamaskController extends EventEmitter {
*/
setupUntrustedCommunication (connectionStream, originDomain) {
// Check if new connection is blacklisted
if (this.blacklistController.checkForPhishing(originDomain)) {
if (this.phishingController.test(originDomain)) {
log.debug('MetaMask - sending phishing warning for', originDomain)
this.sendPhishingWarning(connectionStream, originDomain)
return
@ -1278,8 +1321,9 @@ module.exports = class MetamaskController extends EventEmitter {
// setup multiplexing
const mux = setupMultiplex(connectionStream)
// connect features
this.setupProviderConnection(mux.createStream('provider'), originDomain)
this.setupPublicConfig(mux.createStream('publicConfig'))
const publicApi = this.setupPublicApi(mux.createStream('publicApi'), originDomain)
this.setupProviderConnection(mux.createStream('provider'), originDomain, publicApi)
this.setupPublicConfig(mux.createStream('publicConfig'), originDomain)
}
/**
@ -1352,7 +1396,7 @@ module.exports = class MetamaskController extends EventEmitter {
* @param {*} outStream - The stream to provide over.
* @param {string} origin - The URI of the requesting resource.
*/
setupProviderConnection (outStream, origin) {
setupProviderConnection (outStream, origin, publicApi) {
// setup json rpc engine stack
const engine = new RpcEngine()
const provider = this.provider
@ -1372,8 +1416,13 @@ module.exports = class MetamaskController extends EventEmitter {
engine.push(subscriptionManager.middleware)
// watch asset
engine.push(this.preferencesController.requestWatchAsset.bind(this.preferencesController))
// requestAccounts
engine.push(this.providerApprovalController.createMiddleware({
origin,
getSiteMetadata: publicApi && publicApi.getSiteMetadata,
}))
// forward to metamask primary provider
engine.push(createProviderMiddleware({ provider }))
engine.push(providerAsMiddleware(provider))
// setup connection
const providerStream = createEngineStream({ engine })
@ -1400,18 +1449,56 @@ module.exports = class MetamaskController extends EventEmitter {
*
* @param {*} outStream - The stream to provide public config over.
*/
setupPublicConfig (outStream) {
const configStream = asStream(this.publicConfigStore)
setupPublicConfig (outStream, originDomain) {
const configStore = this.createPublicConfigStore({
// check the providerApprovalController's approvedOrigins
checkIsEnabled: () => this.providerApprovalController.shouldExposeAccounts(originDomain),
})
const configStream = asStream(configStore)
pump(
configStream,
outStream,
(err) => {
configStore.destroy()
configStream.destroy()
if (err) log.error(err)
}
)
}
/**
* A method for providing our public api over a stream.
* This includes a method for setting site metadata like title and image
*
* @param {*} outStream - The stream to provide the api over.
*/
setupPublicApi (outStream) {
const dnode = Dnode()
// connect dnode api to remote connection
pump(
outStream,
dnode,
outStream,
(err) => {
// report any error
if (err) log.error(err)
}
)
const getRemote = createDnodeRemoteGetter(dnode)
const publicApi = {
// wrap with an await remote
getSiteMetadata: async () => {
const remote = await getRemote()
return await pify(remote.getSiteMetadata)()
},
}
return publicApi
}
/**
* Handle a KeyringController update
* @param {object} state the KC state
@ -1544,7 +1631,7 @@ module.exports = class MetamaskController extends EventEmitter {
* @property {string} depositType - An abbreviation of the type of crypto currency to be deposited.
*/
createShapeShiftTx (depositAddress, depositType) {
this.shapeshiftController.createShapeShiftTx(depositAddress, depositType)
this.shapeshiftController.createTransaction(depositAddress, depositType)
}
// network
@ -1557,9 +1644,9 @@ module.exports = class MetamaskController extends EventEmitter {
* @returns {Promise<String>} - The RPC Target URL confirmed.
*/
async updateAndSetCustomRpc (rpcUrl, chainId, ticker = 'ETH', nickname) {
await this.preferencesController.updateRpc({ rpcUrl, chainId, ticker, nickname })
this.networkController.setRpcTarget(rpcUrl, chainId, ticker, nickname)
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)
return rpcUrl
}
@ -1572,15 +1659,15 @@ module.exports = class MetamaskController extends EventEmitter {
* @param {string} nickname - Optional nickname of the selected network.
* @returns {Promise<String>} - The RPC Target URL confirmed.
*/
async setCustomRpc (rpcTarget, chainId, ticker = 'ETH', nickname = '') {
async setCustomRpc (rpcTarget, chainId, ticker = 'ETH', nickname = '', rpcPrefs = {}) {
const frequentRpcListDetail = this.preferencesController.getFrequentRpcListDetail()
const rpcSettings = frequentRpcListDetail.find((rpc) => rpcTarget === rpc.rpcUrl)
if (rpcSettings) {
this.networkController.setRpcTarget(rpcSettings.rpcUrl, rpcSettings.chainId, rpcSettings.ticker, rpcSettings.nickname)
this.networkController.setRpcTarget(rpcSettings.rpcUrl, rpcSettings.chainId, rpcSettings.ticker, rpcSettings.nickname, rpcPrefs)
} else {
this.networkController.setRpcTarget(rpcTarget, chainId, ticker, nickname)
await this.preferencesController.addToFrequentRpcList(rpcTarget, chainId, ticker, nickname)
this.networkController.setRpcTarget(rpcTarget, chainId, ticker, nickname, rpcPrefs)
await this.preferencesController.addToFrequentRpcList(rpcTarget, chainId, ticker, nickname, rpcPrefs)
}
return rpcTarget
}
@ -1705,18 +1792,17 @@ module.exports = class MetamaskController extends EventEmitter {
*/
/**
* Adds a domain to the {@link BlacklistController} whitelist
* Adds a domain to the PhishingController whitelist
* @param {string} hostname the domain to whitelist
*/
whitelistPhishingDomain (hostname) {
return this.blacklistController.whitelistDomain(hostname)
return this.phishingController.bypass(hostname)
}
/**
* Locks MetaMask
*/
setLocked () {
this.providerApprovalController.setLocked()
return this.keyringController.setLocked()
}
}

@ -27,7 +27,7 @@ function transformState (state) {
const newState = state
if (!newState.TransactionController) return newState
const transactions = newState.TransactionController.transactions
newState.TransactionController.transactions = transactions.map((txMeta, _, txList) => {
newState.TransactionController.transactions = transactions.map((txMeta, _) => {
if (
txMeta.status === 'unapproved' &&
txMeta.txParams &&

@ -43,7 +43,7 @@ function normalizeTxParams (txParams) {
// functions that handle normalizing of that key in txParams
const whiteList = {
from: from => ethUtil.addHexPrefix(from).toLowerCase(),
to: to => ethUtil.addHexPrefix(txParams.to).toLowerCase(),
to: () => ethUtil.addHexPrefix(txParams.to).toLowerCase(),
nonce: nonce => ethUtil.addHexPrefix(nonce),
value: value => ethUtil.addHexPrefix(value),
data: data => ethUtil.addHexPrefix(data),

@ -60,20 +60,6 @@ class ExtensionPlatform {
}
}
addMessageListener (cb) {
extension.runtime.onMessage.addListener(cb)
}
sendMessage (message, query = {}) {
const id = query.id
delete query.id
extension.tabs.query({ ...query }, tabs => {
tabs.forEach(tab => {
extension.tabs.sendMessage(id || tab.id, message)
})
})
}
_showConfirmedTransaction (txMeta) {
this._subscribeToNotificationClicked()

@ -1,20 +1,20 @@
module.exports = {
'confirm sig requests': {
signMessage: (msgData, cb) => {
signMessage: (_, cb) => {
const stateUpdate = {
unapprovedMsgs: {},
unapprovedMsgCount: 0,
}
return cb(null, stateUpdate)
},
signPersonalMessage: (msgData, cb) => {
signPersonalMessage: (_, cb) => {
const stateUpdate = {
unapprovedPersonalMsgs: {},
unapprovedPersonalMsgCount: 0,
}
return cb(null, stateUpdate)
},
signTypedMessage: (msgData, cb) => {
signTypedMessage: (_, cb) => {
const stateUpdate = {
unapprovedTypedMessages: {},
unapprovedTypedMessagesCount: 0,

@ -27,20 +27,8 @@ async function start () {
const FIREFOX = `${BUILD_LINK_BASE}/builds/metamask-firefox-${VERSION}.zip`
const EDGE = `${BUILD_LINK_BASE}/builds/metamask-edge-${VERSION}.zip`
const OPERA = `${BUILD_LINK_BASE}/builds/metamask-opera-${VERSION}.zip`
const WALKTHROUGH = `${BUILD_LINK_BASE}/test-artifacts/screens/walkthrough%20%28en%29.gif`
const commentBody = `
<details>
<summary>
Builds ready [${SHORT_SHA1}]:
<a href="${CHROME}">chrome</a>,
<a href="${FIREFOX}">firefox</a>,
<a href="${EDGE}">edge</a>,
<a href="${OPERA}">opera</a>
</summary>
<image src="${WALKTHROUGH}">
</details>
`
const commentBody = `Builds ready [${SHORT_SHA1}]: <a href="${CHROME}">chrome</a>, <a href="${FIREFOX}">firefox</a>, <a href="${EDGE}">edge</a>, <a href="${OPERA}">opera</a>`
const JSON_PAYLOAD = JSON.stringify({ body: commentBody })
const POST_COMMENT_URI = `https://api.github.com/repos/metamask/metamask-extension/issues/${CIRCLE_PR_NUMBER}/comments`

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

@ -133,7 +133,9 @@
"preferences": {
"useNativeCurrencyAsPrimaryCurrency": true,
"showFiatInTestnets": true
}
},
"completedUiMigration": true,
"frequentRpcListDetail": []
},
"appState": {
"menuOpen": false,

@ -156,7 +156,9 @@
"currentLocale": "en",
"preferences": {
"useNativeCurrencyAsPrimaryCurrency": true
}
},
"completedUiMigration": true,
"frequentRpcListDetail": []
},
"appState": {
"menuOpen": false,

@ -115,7 +115,9 @@
"preferences": {
"useNativeCurrencyAsPrimaryCurrency": true,
"showFiatInTestnets": true
}
},
"completedUiMigration": true,
"frequentRpcListDetail": []
},
"appState": {
"menuOpen": false,

@ -230,7 +230,8 @@
"kovan": "ok",
"mainnet": "ok",
"rinkeby": "ok",
"ropsten": "ok"
"ropsten": "ok",
"goerli": "ok"
},
"lostAccounts": []
},
@ -320,4 +321,4 @@
"toSmartContract": false,
"fetchingData": false
}
}
}

@ -704,7 +704,8 @@
"mainnet": "ok",
"ropsten": "ok",
"kovan": "ok",
"rinkeby": "ok"
"rinkeby": "ok",
"goerli": "ok"
},
"shapeShiftTxList": [],
"lostAccounts": []
@ -735,4 +736,4 @@
"os": "mac"
},
"browser": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36"
}
}

@ -137,7 +137,9 @@
"preferences": {
"useNativeCurrencyAsPrimaryCurrency": true,
"showFiatInTestnets": true
}
},
"completedUiMigration": true,
"frequentRpcListDetail": []
},
"appState": {
"menuOpen": false,

@ -116,7 +116,9 @@
"preferences": {
"useNativeCurrencyAsPrimaryCurrency": true,
"showFiatInTestnets": true
}
},
"completedUiMigration": true,
"frequentRpcListDetail": []
},
"appState": {
"menuOpen": false,

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

@ -1058,7 +1058,9 @@
"currentLocale": "en",
"preferences": {
"useNativeCurrencyAsPrimaryCurrency": true
}
},
"completedUiMigration": true,
"frequentRpcListDetail": []
},
"appState": {
"menuOpen": false,

@ -0,0 +1,71 @@
## Creating Metrics Events
The `metricsEvent` method is made available to all components via context. This is done in `metamask-extension/ui/app/helpers/higher-order-components/metametrics/metametrics.provider.js`. As such, it can be called in all components by first adding it to the context proptypes:
```
static contextTypes = {
t: PropTypes.func,
metricsEvent: PropTypes.func,
}
```
and then accessing it on `this.context`.
Below is an example of a metrics event call:
```
this.context.metricsEvent({
eventOpts: {
category: 'Navigation',
action: 'Main Menu',
name: 'Switched Account',
},
})
```
### Base Schema
Every `metricsEvent` call is passed an object that must have an `eventOpts` property. This property is an object that itself must have three properties:
- category: categorizes events according to the schema we have set up in our matomo.org instance
- action: usually describes the page on which the event takes place, or sometimes a significant subsections of a page
- name: a very specific descriptor of the event
### Implicit properties
All metrics events send the following data when called:
- network
- environmentType
- activeCurrency
- accountType
- numberOfTokens
- numberOfAccounts
These are added to the metrics event via the metametrics provider.
### Custom Variables
Metrics events can include custom variables. These are included within the `customVariables` property that is a first-level property within first param passed to `metricsEvent`.
For example:
```
this.context.metricsEvent({
eventOpts: {
category: 'Settings',
action: 'Custom RPC',
name: 'Error',
},
customVariables: {
networkId: newRpc,
chainId,
},
})
```
Custom variables can have custom property names and values can be strings or numbers.
**To include a custom variable, there are a set of necessary steps you must take.**
1. First you must declare a constant equal to the desired name of the custom variable property in `metamask-extension/ui/app/helpers/utils/metametrics.util.js` under `//Custom Variable Declarations`
1. Then you must add that name to the `customVariableNameIdMap` declaration
1. The id must be between 1 and 5
1. There can be no more than 5 custom variables assigned ids on a given url

@ -0,0 +1,5 @@
# MetaMask Design System
A design system is a series of components that can be reused in different combinations. Design systems allow you to manage design at scale.
Design System [Figma File](https://www.figma.com/file/aWgwMrzdAuv9VuPdtst64uuw/Style-Guide?node-id=211%3A0)

@ -0,0 +1,5 @@
# Google Chrome/Brave Limited Site Access for Extensions
Problem: MetaMask doesn't work with limited site access enabled under Chrome's extensions.
Solution: In addition to the site you wish to whitelist, you must add 'api.infura.io' as another domain, so the MetaMask extension is authorized to make RPC calls to Infura.

@ -10,7 +10,7 @@ The `metamask-background` describes the file at `app/scripts/background.js`, whi
When a new site is visited, the WebExtension creates a new `ContentScript` in that page's context, which can be seen at `app/scripts/contentscript.js`. This script represents a per-page setup process, which creates the per-page `web3` api, connects it to the background script via the Port API (wrapped in a [stream abstraction](https://github.com/substack/stream-handbook)), and injected into the DOM before anything loads.
The most confusing part about porting MetaMask to a new platform is the way we provide the Web3 API over a series of streams between contexts. Once you understand how we create the [InpageProvider](../app/scripts/lib/inpage-provider.js) in the [inpage.js script](../app/scripts/inpage.js), you will be able to understand how the [port-stream](../app/scripts/lib/port-stream.js) is just a thin wrapper around the [postMessage API](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage), and a similar stream API can be wrapped around any communication channel to communicate with the `MetaMaskController` via its `setupUntrustedCommunication(stream, domain)` method.
The most confusing part about porting MetaMask to a new platform is the way we provide the Web3 API over a series of streams between contexts. Once you understand how we create the [MetamaskInpageProvider](https://github.com/MetaMask/metamask-inpage-provider/blob/master/index.js) in the [inpage.js script](../app/scripts/inpage.js), you will be able to understand how the [port-stream](../app/scripts/lib/port-stream.js) is just a thin wrapper around the [postMessage API](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage), and a similar stream API can be wrapped around any communication channel to communicate with the `MetaMaskController` via its `setupUntrustedCommunication(stream, domain)` method.
### The MetaMask Controller
@ -89,7 +89,7 @@ MetaMask has two kinds of [duplex stream APIs](https://github.com/substack/strea
If you are making a MetaMask-powered browser for a new platform, one of the trickiest tasks will be injecting the Web3 API into websites that are visited. On WebExtensions, we actually have to pipe data through a total of three JS contexts just to let sites talk to our background process (site -> contentscript -> background).
To see how we do that, you can refer to the [inpage script](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/inpage.js) that we inject into every website. There you can see it creates a multiplex stream to the background, and uses it to initialize what we call the [inpage-provider](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/lib/inpage-provider.js), which you can see stubs a few methods out, but mostly just passes calls to `sendAsync` through the stream it's passed! That's really all the magic that's needed to create a web3-like API in a remote context, once you have a stream to MetaMask available.
To see how we do that, you can refer to the [inpage script](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/inpage.js) that we inject into every website. There you can see it creates a multiplex stream to the background, and uses it to initialize what we call the [MetamaskInpageProvider](https://github.com/MetaMask/metamask-inpage-provider/blob/master/index.js), which you can see stubs a few methods out, but mostly just passes calls to `sendAsync` through the stream it's passed! That's really all the magic that's needed to create a web3-like API in a remote context, once you have a stream to MetaMask available.
In `inpage.js` you can see we create a `PortStream`, that's just a class we use to wrap WebExtension ports as streams, so we can reuse our favorite stream abstraction over the more irregular API surface of the WebExtension. In a new platform, you will probably need to construct this stream differently. The key is that you need to construct a stream that talks from the site context to the background. Once you have that set up, it works like magic!

@ -2,6 +2,13 @@
When publishing a new version of MetaMask, we follow this procedure:
## Overview
The below diagram outlines our process for design, development, and release. Building MetaMask is a community affair, and many steps of the process invite participation from external contributors as indicated. All QA, code review, and release of new versions is done by members of the core MetaMask team.
<img width="664" alt="mm-dev-process" src="https://user-images.githubusercontent.com/1016190/56308059-36906000-60fb-11e9-8e61-6655bca0c54f.png">
## Preparation
We try to ensure certain criteria are met before deploying:
@ -15,7 +22,7 @@ We try to ensure certain criteria are met before deploying:
Version can be automatically incremented [using our bump script](./bumping-version.md).
npm run version:bump $BUMP_TYPE` where `$BUMP_TYPE` is one of `major`, `minor`, or `patch`.
npm run version:bump `$BUMP_TYPE` where `$BUMP_TYPE` is one of `major`, `minor`, or `patch`.
## Preparing for Sensitive Changes

@ -48,11 +48,11 @@ async function start (fileRegEx, testGenerator) {
}
*/
async function startContainer (fileRegEx, testGenerator) {
async function startContainer (fileRegEx) {
const fileNames = await getAllFileNames('./ui/app')
const sFiles = fileNames.filter(name => name.match(fileRegEx))
async.each(sFiles, async (sFile, cb) => {
async.each(sFiles, async (sFile) => {
console.log(`sFile`, sFile)
const [, sRootPath, sPath] = sFile.match(/^(.+\/)([^/]+)$/)
@ -91,7 +91,7 @@ async function startContainer (fileRegEx, testGenerator) {
const proxyquireObject = ('{\n ' + result
.match(/import\s{[\s\S]+?}\sfrom\s.+/g)
.map(s => s.replace(/\n/g, ''))
.map((s, i) => {
.map((s) => {
const proxyKeys = s.match(/{.+}/)[0].match(/\w+/g)
return '\'' + s.match(/'(.+)'/)[1] + '\': { ' + (proxyKeys.length > 1
? '\n ' + proxyKeys.join(': () => {},\n ') + ': () => {},\n '

@ -315,7 +315,7 @@ createTasksForBuildJsExtension({ buildJsFiles, taskPrefix: 'dev:test-extension:j
createTasksForBuildJsExtension({ buildJsFiles, taskPrefix: 'build:extension:js' })
createTasksForBuildJsExtension({ buildJsFiles, taskPrefix: 'build:test:extension:js', testing: 'true' })
function createTasksForBuildJsUIDeps ({ dependenciesToBundle, filename }) {
function createTasksForBuildJsUIDeps ({ filename }) {
const destinations = browserPlatforms.map(platform => `./dist/${platform}`)

450
package-lock.json generated

@ -1746,6 +1746,16 @@
}
}
},
"@types/invariant": {
"version": "2.2.29",
"resolved": "https://registry.npmjs.org/@types/invariant/-/invariant-2.2.29.tgz",
"integrity": "sha512-lRVw09gOvgviOfeUrKc/pmTiRZ7g7oDOU6OAutyuSHpm1/o2RaBQvRhgK8QEdu+FFuw/wnWb29A/iuxv9i8OpQ=="
},
"@types/lodash": {
"version": "4.14.124",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.124.tgz",
"integrity": "sha512-6bKEUVbHJ8z34jisA7lseJZD2g31SIvee3cGX2KEZCS4XXWNbjPZpmO1/2rGNR9BhGtaYr6iYXPl1EzRrDAFTA=="
},
"@types/node": {
"version": "8.5.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.5.5.tgz",
@ -1768,6 +1778,14 @@
"@types/react": "16.3.14"
}
},
"@types/redux": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/@types/redux/-/redux-3.6.0.tgz",
"integrity": "sha1-8evh5UEVGAcuT9/KXHbhbnTBOZo=",
"requires": {
"redux": "*"
}
},
"@yarnpkg/lockfile": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz",
@ -8907,6 +8925,55 @@
}
}
},
"disposables": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/disposables/-/disposables-1.0.2.tgz",
"integrity": "sha1-NsamdEdfVaLWkTVnpgFETkh7S24="
},
"dnd-core": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-3.0.2.tgz",
"integrity": "sha1-6UdXdiBTHH7jelGM1d3hfQ798PM=",
"requires": {
"@types/invariant": "^2.2.29",
"@types/lodash": "^4.14.107",
"@types/node": "^8.10.11",
"@types/redux": "^3.6.0",
"asap": "^2.0.6",
"invariant": "^2.0.0",
"lodash": "^4.2.0",
"redux": "^4.0.0"
},
"dependencies": {
"@types/node": {
"version": "8.10.48",
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.48.tgz",
"integrity": "sha512-c35YEBTkL4rzXY2ucpSKy+UYHjUBIIkuJbWYbsGIrKLEWU5dgJMmLkkIb3qeC3O3Tpb1ZQCwecscvJTDjDjkRw=="
},
"loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"requires": {
"js-tokens": "^3.0.0 || ^4.0.0"
}
},
"redux": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.0.1.tgz",
"integrity": "sha512-R7bAtSkk7nY6O/OYMVR9RiBI+XghjF9rlbl5806HJbQph0LJVHZrU5oaO4q70eUKiqMRqm4y07KLTlMZ2BlVmg==",
"requires": {
"loose-envify": "^1.4.0",
"symbol-observable": "^1.2.0"
}
},
"symbol-observable": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
"integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ=="
}
}
},
"dnode": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/dnode/-/dnode-1.2.2.tgz",
@ -9413,7 +9480,7 @@
},
"engine.io-client": {
"version": "3.2.1",
"resolved": "http://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.2.1.tgz",
"integrity": "sha512-y5AbkytWeM4jQr7m/koQLc5AxpRKC1hEVUb/s1FUAWEJq5AzJJ4NLvzuKPuxtDi5Mq755WuDvZ6Iv2rXj4PTzw==",
"dev": true,
"requires": {
@ -10032,7 +10099,7 @@
},
"inquirer": {
"version": "0.12.0",
"resolved": "http://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz",
"integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=",
"dev": true,
"requires": {
@ -10125,7 +10192,7 @@
},
"table": {
"version": "3.8.3",
"resolved": "http://registry.npmjs.org/table/-/table-3.8.3.tgz",
"resolved": "https://registry.npmjs.org/table/-/table-3.8.3.tgz",
"integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=",
"dev": true,
"requires": {
@ -11207,6 +11274,7 @@
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz",
"integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==",
"dev": true,
"requires": {
"bn.js": "4.11.8",
"create-hash": "1.1.3",
@ -14170,9 +14238,9 @@
"integrity": "sha1-8ESOgGmFW/Kj5oPNwdMg5+KgfvQ="
},
"gaba": {
"version": "1.0.0-beta.65",
"resolved": "https://registry.npmjs.org/gaba/-/gaba-1.0.0-beta.65.tgz",
"integrity": "sha512-pX9hMd4RR5AXe7bwIamQEXLJe26fNvjOf7PjkHGKlRjKzBYmxZ03Y/Pa9nklNlG2Shc9sSgB6GXZpYlXNlJRIg==",
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/gaba/-/gaba-1.0.1.tgz",
"integrity": "sha512-67Zoaq6wnaBASIXGfu2L+jzx8m+l1tfn6FAEIZI/pMvn/ymk4V9raeqz73QQKq1fF4WcRy2H1Ru1r45J1tDQoQ==",
"dev": true,
"requires": {
"await-semaphore": "0.1.3",
@ -14294,6 +14362,15 @@
}
}
},
"eth-method-registry": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/eth-method-registry/-/eth-method-registry-1.1.0.tgz",
"integrity": "sha512-jGbbGYd19XJCtoGFtUD2qJYWefKCCbFcu7F/AQ5sJXvqTIVAHnFn3paaV2zhN5t7iyKYp1qxc+ugOky+72xcbg==",
"dev": true,
"requires": {
"ethjs": "^0.3.0"
}
},
"eth-phishing-detect": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/eth-phishing-detect/-/eth-phishing-detect-1.1.13.tgz",
@ -14346,6 +14423,119 @@
"secp256k1": "3.4.0"
}
},
"ethjs": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/ethjs/-/ethjs-0.3.9.tgz",
"integrity": "sha512-gOQzA3tDUjoLpNONSOALJ/rUFtHi5tXl2mholHasF1cvXhoddqi06yU4OJFJu9AGd6n9v9ywzHlYeIKg1t1hdw==",
"dev": true,
"requires": {
"bn.js": "4.11.6",
"ethjs-abi": "0.2.1",
"ethjs-contract": "0.2.2",
"ethjs-filter": "0.1.8",
"ethjs-provider-http": "0.1.6",
"ethjs-query": "0.3.7",
"ethjs-unit": "0.1.6",
"ethjs-util": "0.1.3",
"js-sha3": "0.5.5",
"number-to-bn": "1.7.0"
},
"dependencies": {
"bn.js": {
"version": "4.11.6",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz",
"integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU=",
"dev": true
},
"ethjs-query": {
"version": "0.3.7",
"resolved": "https://registry.npmjs.org/ethjs-query/-/ethjs-query-0.3.7.tgz",
"integrity": "sha512-TZnKUwfkWjy0SowFdPLtmsytCorHi0i4vvkQn7Jg8rZt33cRzKhuzOwKr/G3vdigCc+ePXOhUGMcJSAPlOG44A==",
"dev": true,
"requires": {
"ethjs-format": "0.2.7",
"ethjs-rpc": "0.2.0",
"promise-to-callback": "^1.0.0"
}
},
"ethjs-util": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.3.tgz",
"integrity": "sha1-39XqSkANxeQhqInK9H4IGtp4u1U=",
"dev": true,
"requires": {
"is-hex-prefixed": "1.0.0",
"strip-hex-prefix": "1.0.0"
}
}
}
},
"ethjs-abi": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/ethjs-abi/-/ethjs-abi-0.2.1.tgz",
"integrity": "sha1-4KepOn6BFjqUR3utVu3lJKtt5TM=",
"dev": true,
"requires": {
"bn.js": "4.11.6",
"js-sha3": "0.5.5",
"number-to-bn": "1.7.0"
},
"dependencies": {
"bn.js": {
"version": "4.11.6",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz",
"integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU=",
"dev": true
}
}
},
"ethjs-contract": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/ethjs-contract/-/ethjs-contract-0.2.2.tgz",
"integrity": "sha512-xxPqEjsULQ/QNWuvX6Ako0PGs5RxALA8N/H3+boLvnaXDFZVGpD7H63H1gBCRTZyYqCldPpVlVHuw/rD45vazw==",
"dev": true,
"requires": {
"ethjs-abi": "0.2.0",
"ethjs-filter": "0.1.8",
"ethjs-util": "0.1.3",
"js-sha3": "0.5.5"
},
"dependencies": {
"bn.js": {
"version": "4.11.6",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz",
"integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU=",
"dev": true
},
"ethjs-abi": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/ethjs-abi/-/ethjs-abi-0.2.0.tgz",
"integrity": "sha1-0+LCIQEVIPxJm3FoIDbBT8wvWyU=",
"dev": true,
"requires": {
"bn.js": "4.11.6",
"js-sha3": "0.5.5",
"number-to-bn": "1.7.0"
}
},
"ethjs-util": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.3.tgz",
"integrity": "sha1-39XqSkANxeQhqInK9H4IGtp4u1U=",
"dev": true,
"requires": {
"is-hex-prefixed": "1.0.0",
"strip-hex-prefix": "1.0.0"
}
}
}
},
"ethjs-filter": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/ethjs-filter/-/ethjs-filter-0.1.8.tgz",
"integrity": "sha512-qTDPskDL2UadHwjvM8A+WG9HwM4/FoSY3p3rMJORkHltYcAuiQZd2otzOYKcL5w2Q3sbAkW/E3yt/FPFL/AVXA==",
"dev": true
},
"ethjs-query": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/ethjs-query/-/ethjs-query-0.3.8.tgz",
@ -14367,6 +14557,12 @@
"promise-to-callback": "1.0.0"
}
},
"js-sha3": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.5.tgz",
"integrity": "sha1-uvDA6MVK1ZA0R9+Wreekobynmko=",
"dev": true
},
"obs-store": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/obs-store/-/obs-store-2.4.1.tgz",
@ -14685,7 +14881,7 @@
"dependencies": {
"jsesc": {
"version": "1.3.0",
"resolved": "http://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz",
"integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=",
"dev": true
},
@ -14854,13 +15050,13 @@
},
"babel-plugin-syntax-async-functions": {
"version": "6.13.0",
"resolved": "http://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz",
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz",
"integrity": "sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=",
"dev": true
},
"babel-plugin-syntax-exponentiation-operator": {
"version": "6.13.0",
"resolved": "http://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz",
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz",
"integrity": "sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4=",
"dev": true
},
@ -15280,7 +15476,7 @@
},
"babelify": {
"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=",
"dev": true,
"requires": {
@ -15367,7 +15563,7 @@
},
"bl": {
"version": "1.2.2",
"resolved": "http://registry.npmjs.org/bl/-/bl-1.2.2.tgz",
"resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz",
"integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==",
"dev": true,
"requires": {
@ -15445,7 +15641,7 @@
},
"browserify-aes": {
"version": "1.2.0",
"resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
"resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
"integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
"dev": true,
"requires": {
@ -15484,7 +15680,7 @@
},
"browserify-rsa": {
"version": "4.0.1",
"resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
"resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
"integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
"dev": true,
"requires": {
@ -15673,7 +15869,7 @@
},
"chalk": {
"version": "1.1.3",
"resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true,
"requires": {
@ -15757,7 +15953,7 @@
},
"commander": {
"version": "2.8.1",
"resolved": "http://registry.npmjs.org/commander/-/commander-2.8.1.tgz",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz",
"integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=",
"dev": true,
"requires": {
@ -15856,7 +16052,7 @@
},
"create-hash": {
"version": "1.2.0",
"resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
"integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
"dev": true,
"requires": {
@ -15869,7 +16065,7 @@
},
"create-hmac": {
"version": "1.1.7",
"resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
"resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
"integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
"dev": true,
"requires": {
@ -15960,7 +16156,7 @@
"dependencies": {
"pify": {
"version": "2.3.0",
"resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true,
"optional": true
@ -16037,14 +16233,14 @@
"dependencies": {
"file-type": {
"version": "3.9.0",
"resolved": "http://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz",
"integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=",
"dev": true,
"optional": true
},
"get-stream": {
"version": "2.3.1",
"resolved": "http://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz",
"integrity": "sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=",
"dev": true,
"optional": true,
@ -16055,7 +16251,7 @@
},
"pify": {
"version": "2.3.0",
"resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true,
"optional": true
@ -16151,7 +16347,7 @@
},
"diffie-hellman": {
"version": "5.0.3",
"resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
"resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
"integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
"dev": true,
"optional": true,
@ -16361,7 +16557,7 @@
"dependencies": {
"pify": {
"version": "2.3.0",
"resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true
}
@ -16382,7 +16578,7 @@
},
"eth-json-rpc-middleware": {
"version": "1.6.0",
"resolved": "http://registry.npmjs.org/eth-json-rpc-middleware/-/eth-json-rpc-middleware-1.6.0.tgz",
"resolved": "https://registry.npmjs.org/eth-json-rpc-middleware/-/eth-json-rpc-middleware-1.6.0.tgz",
"integrity": "sha512-tDVCTlrUvdqHKqivYMjtFZsdD7TtpNLBCfKAcOpaVs7orBMS/A8HWro6dIzNtTZIR05FAbJ3bioFOnZpuCew9Q==",
"dev": true,
"requires": {
@ -16409,7 +16605,7 @@
},
"ethereumjs-block": {
"version": "1.7.1",
"resolved": "http://registry.npmjs.org/ethereumjs-block/-/ethereumjs-block-1.7.1.tgz",
"resolved": "https://registry.npmjs.org/ethereumjs-block/-/ethereumjs-block-1.7.1.tgz",
"integrity": "sha512-B+sSdtqm78fmKkBq78/QLKJbu/4Ts4P2KFISdgcuZUPDm9x+N7qgBPIIFUGbaakQh8bzuquiRVbdmvPKqbILRg==",
"dev": true,
"requires": {
@ -16529,7 +16725,7 @@
},
"ethereumjs-block": {
"version": "1.7.1",
"resolved": "http://registry.npmjs.org/ethereumjs-block/-/ethereumjs-block-1.7.1.tgz",
"resolved": "https://registry.npmjs.org/ethereumjs-block/-/ethereumjs-block-1.7.1.tgz",
"integrity": "sha512-B+sSdtqm78fmKkBq78/QLKJbu/4Ts4P2KFISdgcuZUPDm9x+N7qgBPIIFUGbaakQh8bzuquiRVbdmvPKqbILRg==",
"dev": true,
"requires": {
@ -16561,7 +16757,7 @@
},
"fs-extra": {
"version": "0.30.0",
"resolved": "http://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz",
"integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A=",
"dev": true,
"requires": {
@ -16574,7 +16770,7 @@
},
"pify": {
"version": "2.3.0",
"resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true
},
@ -16599,7 +16795,7 @@
},
"web3-provider-engine": {
"version": "13.8.0",
"resolved": "http://registry.npmjs.org/web3-provider-engine/-/web3-provider-engine-13.8.0.tgz",
"resolved": "https://registry.npmjs.org/web3-provider-engine/-/web3-provider-engine-13.8.0.tgz",
"integrity": "sha512-fZXhX5VWwWpoFfrfocslyg6P7cN3YWPG/ASaevNfeO80R+nzgoPUBXcWQekSGSsNDkeRTis4aMmpmofYf1TNtQ==",
"dev": true,
"requires": {
@ -16644,7 +16840,7 @@
"dependencies": {
"ethereumjs-util": {
"version": "4.5.0",
"resolved": "http://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-4.5.0.tgz",
"resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-4.5.0.tgz",
"integrity": "sha1-PpQosxfuvaPXJg2FT93alUsfG8Y=",
"dev": true,
"requires": {
@ -17053,7 +17249,7 @@
},
"finalhandler": {
"version": "1.1.1",
"resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz",
"integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==",
"dev": true,
"requires": {
@ -17677,7 +17873,7 @@
},
"get-stream": {
"version": "3.0.0",
"resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=",
"dev": true
},
@ -17876,7 +18072,7 @@
},
"http-errors": {
"version": "1.6.3",
"resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
"integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
"dev": true,
"requires": {
@ -18118,7 +18314,7 @@
},
"jsesc": {
"version": "0.5.0",
"resolved": "http://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
"integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=",
"dev": true
},
@ -18180,13 +18376,13 @@
},
"json5": {
"version": "0.5.1",
"resolved": "http://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
"resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
"integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=",
"dev": true
},
"jsonfile": {
"version": "2.4.0",
"resolved": "http://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz",
"integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=",
"dev": true,
"requires": {
@ -18268,7 +18464,7 @@
},
"level-iterator-stream": {
"version": "1.3.1",
"resolved": "http://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-1.3.1.tgz",
"resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-1.3.1.tgz",
"integrity": "sha1-5Dt4sagUPm+pek9IXrjqUwNS8u0=",
"dev": true,
"requires": {
@ -18289,7 +18485,7 @@
},
"readable-stream": {
"version": "1.1.14",
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
"dev": true,
"requires": {
@ -18359,7 +18555,7 @@
"dependencies": {
"readable-stream": {
"version": "1.0.34",
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
"integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
"dev": true,
"requires": {
@ -18426,7 +18622,7 @@
},
"load-json-file": {
"version": "1.1.0",
"resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
"integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
"dev": true,
"requires": {
@ -18439,7 +18635,7 @@
"dependencies": {
"pify": {
"version": "2.3.0",
"resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true
}
@ -18525,7 +18721,7 @@
},
"media-typer": {
"version": "0.3.0",
"resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
"dev": true
},
@ -18568,7 +18764,7 @@
},
"merkle-patricia-tree": {
"version": "2.3.1",
"resolved": "http://registry.npmjs.org/merkle-patricia-tree/-/merkle-patricia-tree-2.3.1.tgz",
"resolved": "https://registry.npmjs.org/merkle-patricia-tree/-/merkle-patricia-tree-2.3.1.tgz",
"integrity": "sha512-Qp9Mpb3xazznXzzGQBqHbqCpT2AR9joUOHYYPiQjYCarrdCPCnLWXo4BFv77y4xN26KR224xoU1n/qYY7RYYgw==",
"dev": true,
"requires": {
@ -18584,7 +18780,7 @@
"dependencies": {
"async": {
"version": "1.5.2",
"resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz",
"resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
"dev": true
},
@ -18696,13 +18892,13 @@
},
"minimist": {
"version": "0.0.8",
"resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
"dev": true
},
"mkdirp": {
"version": "0.5.1",
"resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"dev": true,
"requires": {
@ -18753,7 +18949,7 @@
},
"nan": {
"version": "2.10.0",
"resolved": "http://registry.npmjs.org/nan/-/nan-2.10.0.tgz",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz",
"integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA=="
},
"nano-json-stream-parser": {
@ -18770,7 +18966,7 @@
},
"node-fetch": {
"version": "2.1.2",
"resolved": "http://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz",
"integrity": "sha1-q4hOjn5X44qUR1POxwb3iNF2i7U=",
"dev": true
},
@ -18863,13 +19059,13 @@
},
"os-homedir": {
"version": "1.0.2",
"resolved": "http://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
"resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
"integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
"dev": true
},
"os-locale": {
"version": "1.4.0",
"resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
"resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
"integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=",
"dev": true,
"requires": {
@ -18878,7 +19074,7 @@
},
"os-tmpdir": {
"version": "1.0.2",
"resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
"dev": true
},
@ -18953,7 +19149,7 @@
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
},
@ -18982,7 +19178,7 @@
"dependencies": {
"pify": {
"version": "2.3.0",
"resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true
}
@ -19184,7 +19380,7 @@
},
"query-string": {
"version": "5.1.1",
"resolved": "http://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz",
"integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==",
"dev": true,
"requires": {
@ -19260,7 +19456,7 @@
},
"readable-stream": {
"version": "2.3.6",
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"dev": true,
"requires": {
@ -19326,7 +19522,7 @@
},
"regjsgen": {
"version": "0.2.0",
"resolved": "http://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz",
"resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz",
"integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=",
"dev": true
},
@ -19632,7 +19828,7 @@
},
"sha.js": {
"version": "2.4.11",
"resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
"resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
"integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
"dev": true,
"requires": {
@ -19797,7 +19993,7 @@
},
"strip-ansi": {
"version": "3.0.1",
"resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"dev": true,
"requires": {
@ -19883,7 +20079,7 @@
"dependencies": {
"minimist": {
"version": "1.2.0",
"resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"dev": true
}
@ -19891,7 +20087,7 @@
},
"tar": {
"version": "2.2.1",
"resolved": "http://registry.npmjs.org/tar/-/tar-2.2.1.tgz",
"resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz",
"integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=",
"dev": true,
"optional": true,
@ -19932,7 +20128,7 @@
"dependencies": {
"bluebird": {
"version": "2.11.0",
"resolved": "http://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz",
"integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE=",
"dev": true,
"optional": true
@ -19959,7 +20155,7 @@
},
"through": {
"version": "2.3.8",
"resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
"dev": true
},
@ -20391,7 +20587,7 @@
},
"uuid": {
"version": "2.0.1",
"resolved": "http://registry.npmjs.org/uuid/-/uuid-2.0.1.tgz",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.1.tgz",
"integrity": "sha1-wqMN7bPlNdcsz4LjQ5QaULqFM6w=",
"dev": true,
"optional": true
@ -20622,7 +20818,7 @@
},
"utf8": {
"version": "2.1.1",
"resolved": "http://registry.npmjs.org/utf8/-/utf8-2.1.1.tgz",
"resolved": "https://registry.npmjs.org/utf8/-/utf8-2.1.1.tgz",
"integrity": "sha1-LgHbAvfY0JRPdxBPFgnrDDBM92g=",
"dev": true
}
@ -20653,7 +20849,7 @@
},
"whatwg-fetch": {
"version": "2.0.4",
"resolved": "http://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz",
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz",
"integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==",
"dev": true
},
@ -20671,7 +20867,7 @@
},
"wrap-ansi": {
"version": "2.1.0",
"resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
"integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
"dev": true,
"requires": {
@ -20761,7 +20957,7 @@
},
"yargs": {
"version": "4.8.1",
"resolved": "http://registry.npmjs.org/yargs/-/yargs-4.8.1.tgz",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-4.8.1.tgz",
"integrity": "sha1-wMQpJMpKqmsObaFznfshZDn53cA=",
"dev": true,
"requires": {
@ -20783,7 +20979,7 @@
},
"yargs-parser": {
"version": "2.4.1",
"resolved": "http://registry.npmjs.org/yargs-parser/-/yargs-parser-2.4.1.tgz",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-2.4.1.tgz",
"integrity": "sha1-hVaN488VD/SfpRgl8DqMiA3cxcQ=",
"dev": true,
"requires": {
@ -28623,6 +28819,37 @@
"underscore": "1.4.4"
}
},
"nonce-tracker": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/nonce-tracker/-/nonce-tracker-1.0.0.tgz",
"integrity": "sha512-hxKokxgLvOZx9A5qPQKwL34G1/YwMC5xJWZHFUKfvwxypkn2nP0KVJjbcoXwY6pXsRRa11KdFEPW61N4YCGnWQ==",
"requires": {
"assert": "^1.4.1",
"await-semaphore": "^0.1.3",
"ethjs-query": "^0.3.8"
},
"dependencies": {
"ethjs-query": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/ethjs-query/-/ethjs-query-0.3.8.tgz",
"integrity": "sha512-/J5JydqrOzU8O7VBOwZKUWXxHDGr46VqNjBCJgBVNNda+tv7Xc8Y2uJc6aMHHVbeN3YOQ7YRElgIc0q1CI02lQ==",
"requires": {
"babel-runtime": "^6.26.0",
"ethjs-format": "0.2.7",
"ethjs-rpc": "0.2.0",
"promise-to-callback": "^1.0.0"
}
},
"ethjs-rpc": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/ethjs-rpc/-/ethjs-rpc-0.2.0.tgz",
"integrity": "sha512-RINulkNZTKnj4R/cjYYtYMnFFaBcVALzbtEJEONrrka8IeoarNB9Jbzn+2rT00Cv8y/CxAI+GgY1d0/i2iQeOg==",
"requires": {
"promise-to-callback": "^1.0.0"
}
}
}
},
"nopt": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
@ -31133,7 +31360,7 @@
},
"po2json": {
"version": "0.4.5",
"resolved": "http://registry.npmjs.org/po2json/-/po2json-0.4.5.tgz",
"resolved": "https://registry.npmjs.org/po2json/-/po2json-0.4.5.tgz",
"integrity": "sha1-R7spUtoy1Yob4vJWpZjuvAt0URg=",
"dev": true,
"requires": {
@ -33584,6 +33811,84 @@
}
}
},
"react-dnd": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-3.0.2.tgz",
"integrity": "sha1-sMI9jYKWn1t740y8T4T6H/xcfdw=",
"requires": {
"disposables": "^1.0.1",
"dnd-core": "^3.0.2",
"hoist-non-react-statics": "^2.5.0",
"invariant": "^2.1.0",
"lodash": "^4.2.0",
"prop-types": "^15.5.10",
"shallowequal": "^1.0.2"
},
"dependencies": {
"hoist-non-react-statics": {
"version": "2.5.5",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz",
"integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw=="
},
"shallowequal": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
}
}
},
"react-dnd-html5-backend": {
"version": "7.4.4",
"resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-7.4.4.tgz",
"integrity": "sha512-X/lP92ateY0glHan8mU0JzjBuZL6VHv2Gc/H9OBBxaf/ZCN1oC16MLKdesqG4x1f/NWFTNtuG3W4B99r5gPVog==",
"requires": {
"dnd-core": "^7.4.4"
},
"dependencies": {
"dnd-core": {
"version": "7.4.4",
"resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-7.4.4.tgz",
"integrity": "sha512-xR8SINDCJG9AmKSjXUMJ1PEl8ih1+xSHH8x4DgBtzScXnEtpCnV1ibDZNV0uyps9VgkXTTbYYzJdF04y0v0e3Q==",
"requires": {
"asap": "^2.0.6",
"invariant": "^2.2.4",
"redux": "^4.0.1"
}
},
"invariant": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
"requires": {
"loose-envify": "^1.0.0"
}
},
"redux": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.0.1.tgz",
"integrity": "sha512-R7bAtSkk7nY6O/OYMVR9RiBI+XghjF9rlbl5806HJbQph0LJVHZrU5oaO4q70eUKiqMRqm4y07KLTlMZ2BlVmg==",
"requires": {
"loose-envify": "^1.4.0",
"symbol-observable": "^1.2.0"
},
"dependencies": {
"loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"requires": {
"js-tokens": "^3.0.0 || ^4.0.0"
}
}
}
},
"symbol-observable": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
"integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ=="
}
}
},
"react-docgen": {
"version": "3.0.0-beta9",
"resolved": "https://registry.npmjs.org/react-docgen/-/react-docgen-3.0.0-beta9.tgz",
@ -33704,6 +34009,11 @@
"react-icon-base": "2.1.0"
}
},
"react-idle-timer": {
"version": "4.2.5",
"resolved": "https://registry.npmjs.org/react-idle-timer/-/react-idle-timer-4.2.5.tgz",
"integrity": "sha512-8B/OwjG8E/DTx1fHYKTpZ4cnCbL9+LOc5I9t8SYe8tbEkP14KChiYg0xPIuyRpO33wUZHcgmQl93CVePaDhmRA=="
},
"react-input-autosize": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-2.1.2.tgz",
@ -36465,7 +36775,7 @@
},
"socket.io-parser": {
"version": "3.2.0",
"resolved": "http://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.2.0.tgz",
"integrity": "sha512-FYiBx7rc/KORMJlgsXysflWx/RIvtqZbyGLlHZvjfmPTPeuD/I8MaW7cfFrj5tRltICJdgwflhfZ3NVVbVLFQA==",
"dev": true,
"requires": {

@ -10,6 +10,8 @@
"start:test": "gulp dev:test",
"build:test": "gulp build:test",
"test": "npm run test:unit && npm run test:integration && npm run lint",
"dapp": "static-server test/e2e/beta/contract-test --port 8080",
"dapp-chain": "shell-parallel -s 'npm run ganache:start -- -b 2' -x 'sleep 5 && static-server test/e2e/beta/contract-test --port 8080'",
"watch:test:unit": "nodemon --exec \"npm run test:unit\" ./test ./app ./ui",
"test:unit": "cross-env METAMASK_ENV=test mocha --exit --require test/setup.js --recursive \"test/unit/**/*.js\" \"ui/app/**/*.test.js\"",
"test:single": "cross-env METAMASK_ENV=test mocha --require test/helper.js",
@ -17,6 +19,8 @@
"test:integration:build": "gulp build:scss",
"test:e2e:drizzle:beta": "SELENIUM_BROWSER=chrome test/e2e/beta/run-drizzle.sh",
"test:e2e:chrome": "SELENIUM_BROWSER=chrome test/e2e/beta/run-all.sh",
"test:web3:chrome": "SELENIUM_BROWSER=chrome test/e2e/beta/run-web3.sh",
"test:web3:firefox": "SELENIUM_BROWSER=firefox test/e2e/beta/run-web3.sh",
"test:e2e:firefox": "SELENIUM_BROWSER=firefox test/e2e/beta/run-all.sh",
"test:screens": "shell-parallel -s 'npm run ganache:start' -x 'sleep 3 && npm run test:screens:run'",
"test:screens:run": "node test/screens/new-ui.js",
@ -83,14 +87,14 @@
"ensnare": "^1.0.0",
"eth-bin-to-ops": "^1.0.1",
"eth-block-tracker": "^4.1.0",
"eth-contract-metadata": "github:MetaMask/eth-contract-metadata#master",
"eth-contract-metadata": "github:MetaMask/eth-contract-metadata#dc68506221859bc90792bc5e0279a6835f2484d8",
"eth-ens-namehash": "^2.0.8",
"eth-hd-keyring": "^1.2.2",
"eth-json-rpc-filters": "^3.0.1",
"eth-json-rpc-filters": "^3.0.3",
"eth-json-rpc-infura": "^3.0.0",
"eth-keyring-controller": "^3.3.1",
"eth-ledger-bridge-keyring": "^0.2.0",
"eth-method-registry": "^1.0.0",
"eth-method-registry": "^1.2.0",
"eth-phishing-detect": "^1.1.4",
"eth-query": "^2.1.2",
"eth-sig-util": "^2.0.2",
@ -130,12 +134,12 @@
"mkdirp": "^0.5.1",
"multihashes": "^0.4.12",
"multiplex": "^6.7.0",
"nonce-tracker": "^1.0.0",
"number-to-bn": "^1.7.0",
"obj-multiplex": "^1.0.0",
"obs-store": "^3.0.2",
"percentile": "^1.2.0",
"pify": "^3.0.0",
"ping-pong-stream": "^1.0.0",
"pojo-migrator": "^2.1.0",
"polyfill-crypto.getrandomvalues": "^1.0.0",
"post-message-stream": "^3.0.0",
@ -149,8 +153,11 @@
"ramda": "^0.24.1",
"react": "^15.6.2",
"react-addons-css-transition-group": "^15.6.0",
"react-dnd": "^3.0.2",
"react-dnd-html5-backend": "^7.4.4",
"react-dom": "^15.6.2",
"react-hyperscript": "^3.0.0",
"react-idle-timer": "^4.2.5",
"react-inspector": "^2.3.0",
"react-markdown": "^3.0.0",
"react-media": "^1.8.0",
@ -223,7 +230,7 @@
"file-loader": "^1.1.11",
"fs-extra": "^6.0.1",
"fs-promise": "^2.0.3",
"gaba": "1.0.0-beta.65",
"gaba": "^1.0.1",
"ganache-cli": "^6.1.0",
"ganache-core": "^2.5.3",
"geckodriver": "^1.14.1",

@ -54,7 +54,8 @@
"mainnet": "degraded",
"ropsten": "ok",
"kovan": "ok",
"rinkeby": "ok"
"rinkeby": "ok",
"goerli": "ok"
}
},
"BlacklistController": {
@ -3050,4 +3051,4 @@
]
}
}
}
}

@ -37,8 +37,10 @@ web3.currentProvider.enable().then(() => {
const createToken = document.getElementById('createToken')
const transferTokens = document.getElementById('transferTokens')
const approveTokens = document.getElementById('approveTokens')
const transferTokensWithoutGas = document.getElementById('transferTokensWithoutGas')
const approveTokensWithoutGas = document.getElementById('approveTokensWithoutGas')
deployButton.addEventListener('click', async function (event) {
deployButton.addEventListener('click', async function () {
document.getElementById('contractStatus').innerHTML = 'Deploying'
var piggybank = await piggybankContract.new(
@ -55,7 +57,7 @@ web3.currentProvider.enable().then(() => {
document.getElementById('contractStatus').innerHTML = 'Deployed'
depositButton.addEventListener('click', function (event) {
depositButton.addEventListener('click', function () {
document.getElementById('contractStatus').innerHTML = 'Deposit initiated'
contract.deposit({ from: web3.eth.accounts[0], value: '0x3782dace9d900000' }, function (result) {
console.log(result)
@ -63,7 +65,7 @@ web3.currentProvider.enable().then(() => {
})
})
withdrawButton.addEventListener('click', function (event) {
withdrawButton.addEventListener('click', function () {
contract.withdraw('0xde0b6b3a7640000', { from: web3.eth.accounts[0] }, function (result) {
console.log(result)
document.getElementById('contractStatus').innerHTML = 'Withdrawn'
@ -75,7 +77,7 @@ web3.currentProvider.enable().then(() => {
console.log(piggybank)
})
sendButton.addEventListener('click', function (event) {
sendButton.addEventListener('click', function () {
web3.eth.sendTransaction({
from: web3.eth.accounts[0],
to: '0x2f318C334780961FB129D2a6c30D0763d9a5C970',
@ -88,7 +90,7 @@ web3.currentProvider.enable().then(() => {
})
createToken.addEventListener('click', async function (event) {
createToken.addEventListener('click', async function () {
var _initialAmount = 100
var _tokenName = 'TST'
var _decimalUnits = 0
@ -124,7 +126,7 @@ web3.currentProvider.enable().then(() => {
})
})
approveTokens.addEventListener('click', function (event) {
approveTokens.addEventListener('click', function () {
contract.approve('0x2f318C334780961FB129D2a6c30D0763d9a5C970', '7', {
from: web3.eth.accounts[0],
to: contract.address,
@ -135,6 +137,29 @@ web3.currentProvider.enable().then(() => {
console.log(result)
})
})
transferTokensWithoutGas.addEventListener('click', function (event) {
console.log(`event`, event)
contract.transfer('0x2f318C334780961FB129D2a6c30D0763d9a5C970', '7', {
from: web3.eth.accounts[0],
to: contract.address,
data: '0xa9059cbb0000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C970000000000000000000000000000000000000000000000000000000000000000a',
gasPrice: '20000000000',
}, function (result) {
console.log('result', result)
})
})
approveTokensWithoutGas.addEventListener('click', function () {
contract.approve('0x2f318C334780961FB129D2a6c30D0763d9a5C970', '7', {
from: web3.eth.accounts[0],
to: contract.address,
data: '0x095ea7b30000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C9700000000000000000000000000000000000000000000000000000000000000005',
gasPrice: '20000000000',
}, function (result) {
console.log(result)
})
})
}
})

@ -27,6 +27,8 @@
<button id="createToken">Create Token</button>
<button id="transferTokens">Transfer Tokens</button>
<button id="approveTokens">Approve Tokens</button>
<button id="transferTokensWithoutGas">Transfer Tokens Without Gas</button>
<button id="approveTokensWithoutGas">Approve Tokens Without Gas</button>
</div>
</div>

@ -27,6 +27,8 @@ describe('Using MetaMask with an existing account', function () {
const testSeedPhrase = 'forum vessel pink push lonely enact gentle tail admit parrot grunt dress'
const testAddress = '0x0Cc5261AB8cE458dc977078A3623E2BaDD27afD3'
const testPrivateKey2 = '14abe6f4aab7f9f626fe981c864d0adeb5685f289ac9270c27b8fd790b4235d6'
const testPrivateKey3 = 'F4EC2590A0C10DE95FBF4547845178910E40F5035320C516A18C117DE02B5669'
const tinyDelayMs = 200
const regularDelayMs = 1000
const largeDelayMs = regularDelayMs * 2
@ -323,11 +325,60 @@ describe('Using MetaMask with an existing account', function () {
})
})
describe('Connects to a Hardware wallet', () => {
it('choose Connect Hardware Wallet from the account menu', async () => {
describe('Imports and removes an account', () => {
it('choose Create Account from the account menu', async () => {
await driver.findElement(By.css('.account-menu__icon')).click()
await delay(regularDelayMs)
const [importAccount] = await findElements(driver, By.xpath(`//div[contains(text(), 'Import Account')]`))
await importAccount.click()
await delay(regularDelayMs)
})
it('enter private key', async () => {
const privateKeyInput = await findElement(driver, By.css('#private-key-box'))
await privateKeyInput.sendKeys(testPrivateKey3)
await delay(regularDelayMs)
const importButtons = await findElements(driver, By.xpath(`//button[contains(text(), 'Import')]`))
await importButtons[0].click()
await delay(regularDelayMs)
})
it('should open the remove account modal', async () => {
const [accountName] = await findElements(driver, By.css('.account-name'))
assert.equal(await accountName.getText(), 'Account 5')
await delay(regularDelayMs)
await driver.findElement(By.css('.account-menu__icon')).click()
await delay(regularDelayMs)
const accountListItems = await findElements(driver, By.css('.account-menu__account'))
assert.equal(accountListItems.length, 5)
const removeAccountIcons = await findElements(driver, By.css('.remove-account-icon'))
await removeAccountIcons[1].click()
await delay(tinyDelayMs)
await findElement(driver, By.css('.confirm-remove-account__account'))
})
it('should remove the account', async () => {
const removeButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Remove')]`))
await removeButton.click()
await delay(regularDelayMs)
const [accountName] = await findElements(driver, By.css('.account-name'))
assert.equal(await accountName.getText(), 'Account 1')
await delay(regularDelayMs)
const accountListItems = await findElements(driver, By.css('.account-menu__account'))
assert.equal(accountListItems.length, 4)
})
})
describe('Connects to a Hardware wallet', () => {
it('choose Connect Hardware Wallet from the account menu', async () => {
const [connectAccount] = await findElements(driver, By.xpath(`//div[contains(text(), 'Connect Hardware Wallet')]`))
await connectAccount.click()
await delay(regularDelayMs)

@ -123,7 +123,7 @@ describe('MetaMask', function () {
})
it('clicks the "I agree" option on the metametrics opt-in screen', async () => {
const optOutButton = await findElement(driver, By.css('.btn-confirm'))
const optOutButton = await findElement(driver, By.css('.btn-primary'))
optOutButton.click()
await delay(largeDelayMs)
})

@ -812,10 +812,31 @@ describe('MetaMask', function () {
await delay(regularDelayMs)
const [gasPriceInput, gasLimitInput] = await findElements(driver, By.css('.advanced-tab__gas-edit-row__input'))
await gasPriceInput.clear()
await gasPriceInput.sendKeys(Key.chord(Key.CONTROL, 'a'))
await delay(50)
await gasPriceInput.sendKeys(Key.BACK_SPACE)
await delay(50)
await gasPriceInput.sendKeys(Key.BACK_SPACE)
await delay(50)
await gasPriceInput.sendKeys('10')
await gasLimitInput.clear()
await delay(50)
await gasLimitInput.sendKeys(Key.chord(Key.CONTROL, 'a'))
await delay(50)
await gasLimitInput.sendKeys(Key.BACK_SPACE)
await delay(50)
await gasLimitInput.sendKeys(Key.BACK_SPACE)
await delay(50)
await gasLimitInput.sendKeys(Key.BACK_SPACE)
await delay(50)
await gasLimitInput.sendKeys(Key.BACK_SPACE)
await delay(50)
await gasLimitInput.sendKeys(Key.BACK_SPACE)
await delay(50)
await gasLimitInput.sendKeys('60001')
await delay(50)
await gasLimitInput.sendKeys(Key.chord(Key.CONTROL, 'e'))
await delay(50)
const save = await findElement(driver, By.xpath(`//button[contains(text(), 'Save')]`))
await save.click()
@ -1175,7 +1196,7 @@ describe('MetaMask', function () {
const transferTokens = await findElement(driver, By.xpath(`//button[contains(text(), 'Approve Tokens')]`))
await transferTokens.click()
await closeAllWindowHandlesExcept(driver, extension)
await closeAllWindowHandlesExcept(driver, [extension, dapp])
await driver.switchTo().window(extension)
await delay(regularDelayMs)
@ -1228,21 +1249,31 @@ describe('MetaMask', function () {
await delay(regularDelayMs)
const [gasPriceInput, gasLimitInput] = await findElements(driver, By.css('.advanced-tab__gas-edit-row__input'))
await gasPriceInput.clear()
await delay(tinyDelayMs)
await gasPriceInput.sendKeys(Key.chord(Key.CONTROL, 'a'))
await delay(50)
await gasPriceInput.sendKeys(Key.BACK_SPACE)
await delay(50)
await gasPriceInput.sendKeys(Key.BACK_SPACE)
await delay(50)
await gasPriceInput.sendKeys('10')
await delay(tinyDelayMs)
await gasLimitInput.clear()
await delay(tinyDelayMs)
await delay(50)
await gasLimitInput.sendKeys(Key.chord(Key.CONTROL, 'a'))
await gasLimitInput.sendKeys('60000')
await delay(50)
await gasLimitInput.sendKeys(Key.BACK_SPACE)
await delay(50)
await gasLimitInput.sendKeys(Key.BACK_SPACE)
await delay(50)
await gasLimitInput.sendKeys(Key.BACK_SPACE)
await delay(50)
await gasLimitInput.sendKeys(Key.BACK_SPACE)
await delay(50)
await gasLimitInput.sendKeys(Key.BACK_SPACE)
await delay(50)
await gasLimitInput.sendKeys('60001')
await delay(50)
await gasLimitInput.sendKeys(Key.chord(Key.CONTROL, 'e'))
// Needed for different behaviour of input in different versions of firefox
const gasLimitInputValue = await gasLimitInput.getAttribute('value')
if (gasLimitInputValue === '600001') {
await gasLimitInput.sendKeys(Key.BACK_SPACE)
}
await delay(50)
const save = await findElement(driver, By.css('.page-container__footer-button'))
await save.click()
@ -1271,6 +1302,105 @@ describe('MetaMask', function () {
})
})
describe('Tranfers a custom token from dapp when no gas value is specified', () => {
it('transfers an already created token, without specifying gas', async () => {
const windowHandles = await driver.getAllWindowHandles()
const extension = windowHandles[0]
const dapp = await switchToWindowWithTitle(driver, 'E2E Test Dapp', windowHandles)
await closeAllWindowHandlesExcept(driver, [extension, dapp])
await delay(regularDelayMs)
await driver.switchTo().window(dapp)
const transferTokens = await findElement(driver, By.xpath(`//button[contains(text(), 'Transfer Tokens Without Gas')]`))
await transferTokens.click()
await closeAllWindowHandlesExcept(driver, [extension, dapp])
await driver.switchTo().window(extension)
await delay(regularDelayMs)
await driver.wait(async () => {
const pendingTxes = await findElements(driver, By.css('.transaction-list__pending-transactions .transaction-list-item'))
return pendingTxes.length === 1
}, 10000)
const [txListItem] = await findElements(driver, By.css('.transaction-list-item'))
const [txListValue] = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
await driver.wait(until.elementTextMatches(txListValue, /-7\s*TST/))
await txListItem.click()
await delay(regularDelayMs)
})
it('submits the transaction', async function () {
await delay(regularDelayMs)
const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`))
await confirmButton.click()
await delay(regularDelayMs)
})
it('finds the transaction in the transactions list', async function () {
await driver.wait(async () => {
const confirmedTxes = await findElements(driver, By.css('.transaction-list__completed-transactions .transaction-list-item'))
return confirmedTxes.length === 4
}, 10000)
const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
await driver.wait(until.elementTextMatches(txValues[0], /-7\s*TST/))
const txStatuses = await findElements(driver, By.css('.transaction-list-item__action'))
await driver.wait(until.elementTextMatches(txStatuses[0], /Sent Tokens/))
})
})
describe('Approves a custom token from dapp when no gas value is specified', () => {
it('approves an already created token', async () => {
const windowHandles = await driver.getAllWindowHandles()
const extension = windowHandles[0]
const dapp = await switchToWindowWithTitle(driver, 'E2E Test Dapp', windowHandles)
await closeAllWindowHandlesExcept(driver, [extension, dapp])
await delay(regularDelayMs)
await driver.switchTo().window(dapp)
await delay(tinyDelayMs)
const transferTokens = await findElement(driver, By.xpath(`//button[contains(text(), 'Approve Tokens Without Gas')]`))
await transferTokens.click()
await closeAllWindowHandlesExcept(driver, extension)
await driver.switchTo().window(extension)
await delay(regularDelayMs)
await driver.wait(async () => {
const pendingTxes = await findElements(driver, By.css('.transaction-list__pending-transactions .transaction-list-item'))
return pendingTxes.length === 1
}, 10000)
const [txListItem] = await findElements(driver, By.css('.transaction-list-item'))
const [txListValue] = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
await driver.wait(until.elementTextMatches(txListValue, /-7\s*TST/))
await txListItem.click()
await delay(regularDelayMs)
})
it('submits the transaction', async function () {
await delay(regularDelayMs)
const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`))
await confirmButton.click()
await delay(regularDelayMs)
})
it('finds the transaction in the transactions list', async function () {
await driver.wait(async () => {
const confirmedTxes = await findElements(driver, By.css('.transaction-list__completed-transactions .transaction-list-item'))
return confirmedTxes.length === 5
}, 10000)
const txValues = await findElements(driver, By.css('.transaction-list-item__amount--primary'))
await driver.wait(until.elementTextMatches(txValues[0], /-7\s*TST/))
const txStatuses = await findElements(driver, By.css('.transaction-list-item__action'))
await driver.wait(until.elementTextMatches(txStatuses[0], /Approve/))
})
})
describe('Hide token', () => {
it('hides the token when clicked', async () => {
const [hideTokenEllipsis] = await findElements(driver, By.css('.token-list-item__ellipsis'))
@ -1341,11 +1471,14 @@ describe('MetaMask', function () {
await customRpcButton.click()
await delay(regularDelayMs)
const customRpcInput = await findElement(driver, By.css('input[placeholder="New RPC URL"]'))
await findElement(driver, By.css('.settings-page__sub-header-text'))
const customRpcInputs = await findElements(driver, By.css('input[type="text"]'))
const customRpcInput = customRpcInputs[1]
await customRpcInput.clear()
await customRpcInput.sendKeys(customRpcUrl)
const customRpcSave = await findElement(driver, By.css('.settings-tab__rpc-save-button'))
const customRpcSave = await findElement(driver, By.css('.page-container__footer-button'))
await customRpcSave.click()
await delay(largeDelayMs * 2)
})

@ -0,0 +1,9 @@
#!/usr/bin/env bash
set -e
set -u
set -o pipefail
export PATH="$PATH:./node_modules/.bin"
shell-parallel -s 'static-server test/web3 --port 8080' -x 'sleep 5 && mocha test/e2e/beta/web3.spec'

@ -0,0 +1,365 @@
const path = require('path')
const assert = require('assert')
const webdriver = require('selenium-webdriver')
const { By } = webdriver
const {
delay,
buildChromeWebDriver,
buildFirefoxWebdriver,
installWebExt,
getExtensionIdChrome,
getExtensionIdFirefox,
} = require('../func')
const {
checkBrowserForConsoleErrors,
closeAllWindowHandlesExcept,
findElement,
findElements,
openNewPage,
switchToWindowWithTitle,
verboseReportOnFailure,
waitUntilXWindowHandles,
} = require('./helpers')
const fetchMockResponses = require('./fetch-mocks.js')
describe('Using MetaMask with an existing account', function () {
let extensionId
let driver
const testSeedPhrase = 'forum vessel pink push lonely enact gentle tail admit parrot grunt dress'
const regularDelayMs = 1000
const largeDelayMs = regularDelayMs * 2
const button = async (x) => {
const buttoncheck = x
await buttoncheck.click()
await delay(largeDelayMs)
const [results] = await findElements(driver, By.css('#results'))
const resulttext = await results.getText()
var parsedData = JSON.parse(resulttext)
return (parsedData)
}
this.timeout(0)
this.bail(true)
before(async function () {
let extensionUrl
switch (process.env.SELENIUM_BROWSER) {
case 'chrome': {
const extensionPath = path.resolve('dist/chrome')
driver = buildChromeWebDriver(extensionPath)
extensionId = await getExtensionIdChrome(driver)
await delay(regularDelayMs)
extensionUrl = `chrome-extension://${extensionId}/home.html`
break
}
case 'firefox': {
const extensionPath = path.resolve('dist/firefox')
driver = buildFirefoxWebdriver()
await installWebExt(driver, extensionPath)
await delay(regularDelayMs)
extensionId = await getExtensionIdFirefox(driver)
extensionUrl = `moz-extension://${extensionId}/home.html`
break
}
}
// Depending on the state of the application built into the above directory (extPath) and the value of
// METAMASK_DEBUG we will see different post-install behaviour and possibly some extra windows. Here we
// are closing any extraneous windows to reset us to a single window before continuing.
const [tab1] = await driver.getAllWindowHandles()
await closeAllWindowHandlesExcept(driver, [tab1])
await driver.switchTo().window(tab1)
await driver.get(extensionUrl)
})
beforeEach(async function () {
await driver.executeScript(
'window.origFetch = window.fetch.bind(window);' +
'window.fetch = ' +
'(...args) => { ' +
'if (args[0] === "https://ethgasstation.info/json/ethgasAPI.json") { return ' +
'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.ethGasBasic + '\')) }); } else if ' +
'(args[0] === "https://ethgasstation.info/json/predictTable.json") { return ' +
'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.ethGasPredictTable + '\')) }); } else if ' +
'(args[0].match(/chromeextensionmm/)) { return ' +
'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.metametrics + '\')) }); } else if ' +
'(args[0] === "https://dev.blockscale.net/api/gasexpress.json") { return ' +
'Promise.resolve({ json: () => Promise.resolve(JSON.parse(\'' + fetchMockResponses.gasExpress + '\')) }); } ' +
'return window.origFetch(...args); };' +
'function cancelInfuraRequest(requestDetails) {' +
'console.log("Canceling: " + requestDetails.url);' +
'return {' +
'cancel: true' +
'};' +
' }' +
'window.chrome && window.chrome.webRequest && window.chrome.webRequest.onBeforeRequest.addListener(' +
'cancelInfuraRequest,' +
'{urls: ["https://*.infura.io/*"]},' +
'["blocking"]' +
');'
)
})
afterEach(async function () {
if (process.env.SELENIUM_BROWSER === 'chrome') {
const errors = await checkBrowserForConsoleErrors(driver)
if (errors.length) {
const errorReports = errors.map(err => err.message)
const errorMessage = `Errors found in browser console:\n${errorReports.join('\n')}`
console.error(new Error(errorMessage))
}
}
if (this.currentTest.state === 'failed') {
await verboseReportOnFailure(driver, this.currentTest)
}
})
after(async function () {
await driver.quit()
})
describe('First time flow starting from an existing seed phrase', () => {
it('clicks the continue button on the welcome screen', async () => {
await findElement(driver, By.css('.welcome-page__header'))
const welcomeScreenBtn = await findElement(driver, By.css('.first-time-flow__button'))
welcomeScreenBtn.click()
await delay(largeDelayMs)
})
it('clicks the "Import Wallet" option', async () => {
const customRpcButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Import Wallet')]`))
customRpcButton.click()
await delay(largeDelayMs)
})
it('clicks the "No thanks" option on the metametrics opt-in screen', async () => {
const optOutButton = await findElement(driver, By.css('.btn-default'))
optOutButton.click()
await delay(largeDelayMs)
})
it('imports a seed phrase', async () => {
const [seedTextArea] = await findElements(driver, By.css('textarea.first-time-flow__textarea'))
await seedTextArea.sendKeys(testSeedPhrase)
await delay(regularDelayMs)
const [password] = await findElements(driver, By.id('password'))
await password.sendKeys('correct horse battery staple')
const [confirmPassword] = await findElements(driver, By.id('confirm-password'))
confirmPassword.sendKeys('correct horse battery staple')
const tosCheckBox = await findElement(driver, By.css('.first-time-flow__checkbox'))
await tosCheckBox.click()
const [importButton] = await findElements(driver, By.xpath(`//button[contains(text(), 'Import')]`))
await importButton.click()
await delay(regularDelayMs)
})
it('clicks through the success screen', async () => {
await findElement(driver, By.xpath(`//div[contains(text(), 'Congratulations')]`))
const doneButton = await findElement(driver, By.css('button.first-time-flow__button'))
await doneButton.click()
await delay(regularDelayMs)
})
})
describe('opens dapp', () => {
it('switches to mainnet', async () => {
const networkDropdown = await findElement(driver, By.css('.network-name'))
await networkDropdown.click()
await delay(regularDelayMs)
const [mainnet] = await findElements(driver, By.xpath(`//span[contains(text(), 'Main Ethereum Network')]`))
await mainnet.click()
await delay(largeDelayMs * 2)
})
it('', async () => {
await openNewPage(driver, 'http://127.0.0.1:8080/')
await delay(regularDelayMs)
await waitUntilXWindowHandles(driver, 3)
const windowHandles = await driver.getAllWindowHandles()
const extension = windowHandles[0]
const popup = await switchToWindowWithTitle(driver, 'MetaMask Notification', windowHandles)
const dapp = windowHandles.find(handle => handle !== extension && handle !== popup)
await delay(regularDelayMs)
const approveButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`))
await approveButton.click()
await driver.switchTo().window(dapp)
await delay(regularDelayMs)
})
})
describe('testing web3 methods', async () => {
it('testing hexa methods', async () => {
var List = await driver.findElements(By.className('hexaNumberMethods'))
for (let i = 0; i < List.length; i++) {
try {
var parsedData = await button(List[i])
console.log(parsedData)
var result = parseInt(parsedData.result, 16)
assert.equal((typeof result === 'number'), true)
await delay(regularDelayMs)
} catch (err) {
console.log(err)
assert(false)
}
}
})
it('testing booleanMethods', async () => {
var List = await driver.findElements(By.className('booleanMethods'))
for (let i = 0; i < List.length; i++) {
try {
var parsedData = await button(List[i])
console.log(parsedData)
var result = parsedData.result
assert.equal(result, false)
await delay(regularDelayMs)
} catch (err) {
console.log(err)
assert(false)
}
}
})
it('testing transactionMethods', async () => {
var List = await driver.findElements(By.className('transactionMethods'))
for (let i = 0; i < List.length; i++) {
try {
var parsedData = await button(List[i])
console.log(parsedData.result.blockHash)
var result = []
result.push(parseInt(parsedData.result.blockHash, 16))
result.push(parseInt(parsedData.result.blockNumber, 16))
result.push(parseInt(parsedData.result.gas, 16))
result.push(parseInt(parsedData.result.gasPrice, 16))
result.push(parseInt(parsedData.result.hash, 16))
result.push(parseInt(parsedData.result.input, 16))
result.push(parseInt(parsedData.result.nonce, 16))
result.push(parseInt(parsedData.result.r, 16))
result.push(parseInt(parsedData.result.s, 16))
result.push(parseInt(parsedData.result.v, 16))
result.push(parseInt(parsedData.result.to, 16))
result.push(parseInt(parsedData.result.value, 16))
result.forEach((value) => {
assert.equal((typeof value === 'number'), true)
})
} catch (err) {
console.log(err)
assert(false)
}
}
})
it('testing blockMethods', async () => {
var List = await driver.findElements(By.className('blockMethods'))
for (let i = 0; i < List.length; i++) {
try {
var parsedData = await button(List[i])
console.log(JSON.stringify(parsedData) + i)
console.log(parsedData.result.parentHash)
var result = parseInt(parsedData.result.parentHash, 16)
assert.equal((typeof result === 'number'), true)
await delay(regularDelayMs)
} catch (err) {
console.log(err)
assert(false)
}
}
})
it('testing methods', async () => {
var List = await driver.findElements(By.className('methods'))
var parsedData
var result
for (let i = 0; i < List.length; i++) {
try {
if (i === 2) {
parsedData = await button(List[i])
console.log(parsedData.result.blockHash)
result = parseInt(parsedData.result.blockHash, 16)
assert.equal((typeof result === 'number' || (result === 0)), true)
await delay(regularDelayMs)
} else {
parsedData = await button(List[i])
console.log(parsedData.result)
result = parseInt(parsedData.result, 16)
assert.equal((typeof result === 'number' || (result === 0)), true)
await delay(regularDelayMs)
}
} catch (err) {
console.log(err)
assert(false)
}
}
})
})
})

@ -15,16 +15,25 @@ QUnit.test('successful confirmation of sig requests', (assert) => {
})
})
async function runConfirmSigRequestsTest (assert, done) {
global.ethQuery = global.ethQuery || {}
async function runConfirmSigRequestsTest (assert) {
const selectState = await queryAsync($, 'select')
selectState.val('confirm sig requests')
reactTriggerChange(selectState[0])
const realFetch = window.fetch.bind(window)
global.fetch = (...args) => {
if (args[0].match(/chromeextensionmm/)) {
if (args[0] === 'https://ethgasstation.info/json/ethgasAPI.json') {
return Promise.resolve({ json: () => Promise.resolve(JSON.parse(fetchMockResponses.ethGasBasic)) })
} else if (args[0] === 'https://ethgasstation.info/json/predictTable.json') {
return Promise.resolve({ json: () => Promise.resolve(JSON.parse(fetchMockResponses.ethGasPredictTable)) })
} else if (args[0] === 'https://dev.blockscale.net/api/gasexpress.json') {
return Promise.resolve({ json: () => Promise.resolve(JSON.parse(fetchMockResponses.gasExpress)) })
} else if (args[0].match(/chromeextensionmm/)) {
return Promise.resolve({ json: () => Promise.resolve(JSON.parse(fetchMockResponses.metametrics)) })
}
return window.fetch(...args)
return realFetch.fetch(...args)
}
const pendingRequestItem = $.find('.transaction-list-item .transaction-list-item__grid')
@ -44,7 +53,7 @@ async function runConfirmSigRequestsTest (assert, done) {
let confirmSigRowValue = await queryAsync($, '.request-signature__row-value')
assert.equal(confirmSigRowValue[0].textContent, '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0')
let confirmSigSignButton = await queryAsync($, 'button.btn-primary.btn--large')
let confirmSigSignButton = await queryAsync($, 'button.btn-secondary.btn--large')
confirmSigSignButton[0].click()
await timeout(1000)
confirmSigHeadline = await queryAsync($, '.request-signature__headline')
@ -53,7 +62,7 @@ async function runConfirmSigRequestsTest (assert, done) {
confirmSigRowValue = await queryAsync($, '.request-signature__row-value')
assert.ok(confirmSigRowValue[0].textContent.match(/^#\sTerms\sof\sUse/))
confirmSigSignButton = await queryAsync($, 'button.btn-primary.btn--large')
confirmSigSignButton = await queryAsync($, 'button.btn-secondary.btn--large')
confirmSigSignButton[0].click()
await timeout(1000)
confirmSigHeadline = await queryAsync($, '.request-signature__headline')
@ -63,7 +72,7 @@ async function runConfirmSigRequestsTest (assert, done) {
assert.equal(confirmSigRowValue[0].textContent, 'Hi, Alice!')
assert.equal(confirmSigRowValue[1].textContent, '1337')
confirmSigSignButton = await queryAsync($, 'button.btn-primary.btn--large')
confirmSigSignButton = await queryAsync($, 'button.btn-secondary.btn--large')
confirmSigSignButton[0].click()
await timeout(2000)

@ -16,16 +16,23 @@ QUnit.test('renders localized currency', (assert) => {
})
})
async function runCurrencyLocalizationTest (assert, done) {
async function runCurrencyLocalizationTest (assert) {
console.log('*** start runCurrencyLocalizationTest')
const selectState = await queryAsync($, 'select')
selectState.val('currency localization')
const realFetch = window.fetch.bind(window)
global.fetch = (...args) => {
if (args[0].match(/chromeextensionmm/)) {
if (args[0] === 'https://ethgasstation.info/json/ethgasAPI.json') {
return Promise.resolve({ json: () => Promise.resolve(JSON.parse(fetchMockResponses.ethGasBasic)) })
} else if (args[0] === 'https://ethgasstation.info/json/predictTable.json') {
return Promise.resolve({ json: () => Promise.resolve(JSON.parse(fetchMockResponses.ethGasPredictTable)) })
} else if (args[0] === 'https://dev.blockscale.net/api/gasexpress.json') {
return Promise.resolve({ json: () => Promise.resolve(JSON.parse(fetchMockResponses.gasExpress)) })
} else if (args[0].match(/chromeextensionmm/)) {
return Promise.resolve({ json: () => Promise.resolve(JSON.parse(fetchMockResponses.metametrics)) })
}
return window.fetch(...args)
return realFetch.fetch(...args)
}
await timeout(1000)

@ -22,9 +22,10 @@ global.ethQuery = {
global.ethereumProvider = {}
async function runSendFlowTest (assert, done) {
async function runSendFlowTest (assert) {
const tempFetch = global.fetch
const realFetch = window.fetch.bind(window)
global.fetch = (...args) => {
if (args[0] === 'https://ethgasstation.info/json/ethgasAPI.json') {
return Promise.resolve({ json: () => Promise.resolve(JSON.parse(fetchMockResponses.ethGasBasic)) })
@ -35,7 +36,7 @@ async function runSendFlowTest (assert, done) {
} else if (args[0].match(/chromeextensionmm/)) {
return Promise.resolve({ json: () => Promise.resolve(JSON.parse(fetchMockResponses.metametrics)) })
}
return window.fetch(...args)
return realFetch.fetch(...args)
}
console.log('*** start runSendFlowTest')
@ -43,16 +44,13 @@ async function runSendFlowTest (assert, done) {
selectState.val('send new ui')
reactTriggerChange(selectState[0])
const sendScreenButton = await queryAsync($, 'button.btn-primary.transaction-view-balance__button')
const sendScreenButton = await queryAsync($, 'button.btn-secondary.transaction-view-balance__button')
assert.ok(sendScreenButton[1], 'send screen button present')
sendScreenButton[1].click()
const sendTitle = await queryAsync($, '.page-container__title')
assert.equal(sendTitle[0].textContent, 'Send ETH', 'Send screen title is correct')
const sendCopy = await queryAsync($, '.page-container__subtitle')
assert.equal(sendCopy[0].textContent, 'Only send ETH to an Ethereum address.', 'Send screen has copy')
const sendFromField = await queryAsync($, '.send-v2__form-field')
assert.ok(sendFromField[0], 'send screen has a from field')
@ -72,12 +70,30 @@ async function runSendFlowTest (assert, done) {
const sendToAccountAddress = sendToFieldInput.val()
assert.equal(sendToAccountAddress, '0x2f8D4a878cFA04A6E60D46362f5644DeAb66572D', 'send to dropdown selects the correct address')
const sendAmountField = await queryAsync($, '.send-v2__form-row:eq(2)')
sendAmountField.find('.unit-input')[0].click()
const sendAmountField = await queryAsync($, '.send-v2__form-row:eq(3)')
const sendAmountFieldInput = await findAsync(sendAmountField, '.unit-input__input')
const amountMaxButton = await queryAsync($, '.send-v2__amount-max')
amountMaxButton.click()
reactTriggerChange(sendAmountField.find('input')[1])
assert.equal(sendAmountFieldInput.is(':disabled'), true, 'disabled the send amount input when max mode is on')
const gasPriceButtonGroup = await queryAsync($, '.gas-price-button-group--small')
const gasPriceButton = await gasPriceButtonGroup.find('button')[0]
const valueBeforeGasPriceChange = sendAmountFieldInput.prop('value')
gasPriceButton.click()
reactTriggerChange(sendAmountField.find('input')[1])
await timeout(1000)
assert.notEqual(valueBeforeGasPriceChange, sendAmountFieldInput.prop('value'), 'send amount value changes when gas price changes')
amountMaxButton.click()
reactTriggerChange(sendAmountField.find('input')[1])
sendAmountField.find('.unit-input').click()
sendAmountFieldInput.val('5.1')
reactTriggerChange(sendAmountField.find('input')[0])
reactTriggerChange(sendAmountField.find('input')[1])
let errorMessage = await queryAsync($, '.send-v2__error')
assert.equal(errorMessage[0].textContent, 'Insufficient funds.', 'send should render an insufficient fund error message')
@ -88,7 +104,7 @@ async function runSendFlowTest (assert, done) {
errorMessage = $('.send-v2__error')
assert.equal(errorMessage.length, 0, 'send should stop rendering amount error message after amount is corrected')
const sendButton = await queryAsync($, 'button.btn-primary.btn--large.page-container__footer-button')
const sendButton = await queryAsync($, 'button.btn-secondary.btn--large.page-container__footer-button')
assert.equal(sendButton[0].textContent, 'Next', 'next button rendered')
sendButton[0].click()
await timeout()
@ -115,14 +131,14 @@ async function runSendFlowTest (assert, done) {
sendToFieldInputInEdit[0].focus()
sendToFieldInputInEdit.val('0xd85a4b6a394794842887b8284293d69163007bbb')
const sendAmountFieldInEdit = await queryAsync($, '.send-v2__form-row:eq(2)')
const sendAmountFieldInEdit = await queryAsync($, '.send-v2__form-row:eq(3)')
sendAmountFieldInEdit.find('.unit-input')[0].click()
const sendAmountFieldInputInEdit = sendAmountFieldInEdit.find('.unit-input__input')
sendAmountFieldInputInEdit.val('1.0')
reactTriggerChange(sendAmountFieldInputInEdit[0])
const sendButtonInEdit = await queryAsync($, '.btn-primary.btn--large.page-container__footer-button')
const sendButtonInEdit = await queryAsync($, '.btn-secondary.btn--large.page-container__footer-button')
assert.equal(sendButtonInEdit[0].textContent, 'Next', 'next button in edit rendered')
selectState.val('send new ui')

@ -20,17 +20,24 @@ global.ethQuery.getTransactionCount = (_, cb) => {
cb(null, '0x4')
}
async function runTxListItemsTest (assert, done) {
async function runTxListItemsTest (assert) {
console.log('*** start runTxListItemsTest')
const selectState = await queryAsync($, 'select')
selectState.val('tx list items')
reactTriggerChange(selectState[0])
const realFetch = window.fetch.bind(window)
global.fetch = (...args) => {
if (args[0].match(/chromeextensionmm/)) {
if (args[0] === 'https://ethgasstation.info/json/ethgasAPI.json') {
return Promise.resolve({ json: () => Promise.resolve(JSON.parse(fetchMockResponses.ethGasBasic)) })
} else if (args[0] === 'https://ethgasstation.info/json/predictTable.json') {
return Promise.resolve({ json: () => Promise.resolve(JSON.parse(fetchMockResponses.ethGasPredictTable)) })
} else if (args[0] === 'https://dev.blockscale.net/api/gasexpress.json') {
return Promise.resolve({ json: () => Promise.resolve(JSON.parse(fetchMockResponses.gasExpress)) })
} else if (args[0].match(/chromeextensionmm/)) {
return Promise.resolve({ json: () => Promise.resolve(JSON.parse(fetchMockResponses.metametrics)) })
}
return window.fetch(...args)
return realFetch.fetch(...args)
}
const metamaskLogo = await queryAsync($, '.app-header__logo-container')

@ -4,12 +4,12 @@ let cacheVal
module.exports = {
encrypt (password, dataObj) {
encrypt (_, dataObj) {
cacheVal = dataObj
return Promise.resolve(mockHex)
},
decrypt (password, text) {
decrypt () {
return Promise.resolve(cacheVal || {})
},
@ -21,7 +21,7 @@ module.exports = {
return this.decrypt(key, text)
},
keyFromPassword (password) {
keyFromPassword () {
return Promise.resolve(mockKey)
},

@ -6,7 +6,7 @@ module.exports = {
}
function timeout (time) {
return new Promise((resolve, reject) => {
return new Promise((resolve) => {
setTimeout(resolve, time || 1500)
})
}

@ -33,8 +33,8 @@ describe('tx confirmation screen', function () {
describe('cancelTx', function () {
before(function (done) {
actions._setBackgroundConnection({
approveTransaction (txId, cb) { cb('An error!') },
cancelTransaction (txId, cb) { cb() },
approveTransaction (_, cb) { cb('An error!') },
cancelTransaction (_, cb) { cb() },
clearSeedWordCache (cb) { cb() },
getState (cb) { cb() },
})

@ -1,56 +0,0 @@
const assert = require('assert')
const BlacklistController = require('../../../../app/scripts/controllers/blacklist')
describe('blacklist controller', function () {
let blacklistController
before(() => {
blacklistController = new BlacklistController()
})
describe('whitelistDomain', function () {
it('should add hostname to the runtime whitelist', function () {
blacklistController.whitelistDomain('foo.com')
assert.deepEqual(blacklistController.store.getState().whitelist, ['foo.com'])
blacklistController.whitelistDomain('bar.com')
assert.deepEqual(blacklistController.store.getState().whitelist, ['bar.com', 'foo.com'])
})
})
describe('checkForPhishing', function () {
it('should not flag whitelisted values', function () {
const result = blacklistController.checkForPhishing('www.metamask.io')
assert.equal(result, false)
})
it('should flag explicit values', function () {
const result = blacklistController.checkForPhishing('metamask.com')
assert.equal(result, true)
})
it('should flag levenshtein values', function () {
const result = blacklistController.checkForPhishing('metmask.io')
assert.equal(result, true)
})
it('should not flag not-even-close values', function () {
const result = blacklistController.checkForPhishing('example.com')
assert.equal(result, false)
})
it('should not flag the ropsten faucet domains', function () {
const result = blacklistController.checkForPhishing('faucet.metamask.io')
assert.equal(result, false)
})
it('should not flag the mascara domain', function () {
const result = blacklistController.checkForPhishing('zero.metamask.io')
assert.equal(result, false)
})
it('should not flag the mascara-faucet domain', function () {
const result = blacklistController.checkForPhishing('zero-faucet.metamask.io')
assert.equal(result, false)
})
it('should not flag whitelisted domain', function () {
blacklistController.whitelistDomain('metamask.com')
const result = blacklistController.checkForPhishing('metamask.com')
assert.equal(result, false)
})
})
})

@ -59,7 +59,7 @@ describe('currency-controller', function () {
var promise = new Promise(
function (resolve, reject) {
function (resolve) {
currencyController.setCurrentCurrency('jpy')
currencyController.updateConversionRate().then(function () {
resolve()

@ -4,7 +4,7 @@ const InfuraController = require('../../../../app/scripts/controllers/infura')
describe('infura-controller', function () {
let infuraController, sandbox, networkStatus
const response = {'mainnet': 'degraded', 'ropsten': 'ok', 'kovan': 'ok', 'rinkeby': 'down'}
const response = {'mainnet': 'degraded', 'ropsten': 'ok', 'kovan': 'ok', 'rinkeby': 'down', 'goerli': 'ok'}
before(async function () {
infuraController = new InfuraController()
@ -58,5 +58,15 @@ describe('infura-controller', function () {
assert.equal(networkStatus.rinkeby, 'down')
})
})
describe('Goerli', function () {
it('should have Goerli', function () {
assert.equal(Object.keys(networkStatus)[4], 'goerli')
})
it('should have a value for Goerli status', function () {
assert.equal(networkStatus.goerli, 'ok')
})
})
})
})

@ -49,7 +49,7 @@ describe('MetaMaskController', function () {
showUnapprovedTx: noop,
showUnconfirmedMessage: noop,
encryptor: {
encrypt: function (password, object) {
encrypt: function (_, object) {
this.object = object
return Promise.resolve('mock-encrypted')
},
@ -144,7 +144,7 @@ describe('MetaMaskController', function () {
sandbox.stub(metamaskController, 'getBalance')
metamaskController.getBalance.callsFake(() => { return Promise.resolve('0x0') })
await metamaskController.createNewVaultAndRestore(password, TEST_SEED.slice(0, -1)).catch((e) => null)
await metamaskController.createNewVaultAndRestore(password, TEST_SEED.slice(0, -1)).catch(() => null)
await metamaskController.createNewVaultAndRestore(password, TEST_SEED)
assert(metamaskController.keyringController.createNewVaultAndRestore.calledTwice)
@ -207,7 +207,7 @@ describe('MetaMaskController', function () {
const accounts = {}
const balance = '0x14ced5122ce0a000'
const ethQuery = new EthQuery()
sinon.stub(ethQuery, 'getBalance').callsFake((account, callback) => {
sinon.stub(ethQuery, 'getBalance').callsFake((_, callback) => {
callback(undefined, balance)
})
@ -295,7 +295,7 @@ describe('MetaMaskController', function () {
it('should add the Trezor Hardware keyring', async function () {
sinon.spy(metamaskController.keyringController, 'addNewKeyring')
await metamaskController.connectHardware('trezor', 0).catch((e) => null)
await metamaskController.connectHardware('trezor', 0).catch(() => null)
const keyrings = await metamaskController.keyringController.getKeyringsByType(
'Trezor Hardware'
)
@ -305,7 +305,7 @@ describe('MetaMaskController', function () {
it('should add the Ledger Hardware keyring', async function () {
sinon.spy(metamaskController.keyringController, 'addNewKeyring')
await metamaskController.connectHardware('ledger', 0).catch((e) => null)
await metamaskController.connectHardware('ledger', 0).catch(() => null)
const keyrings = await metamaskController.keyringController.getKeyringsByType(
'Ledger Hardware'
)
@ -325,7 +325,7 @@ describe('MetaMaskController', function () {
})
it('should be locked by default', async function () {
await metamaskController.connectHardware('trezor', 0).catch((e) => null)
await metamaskController.connectHardware('trezor', 0).catch(() => null)
const status = await metamaskController.checkHardwareStatus('trezor')
assert.equal(status, false)
})
@ -341,7 +341,7 @@ describe('MetaMaskController', function () {
})
it('should wipe all the keyring info', async function () {
await metamaskController.connectHardware('trezor', 0).catch((e) => null)
await metamaskController.connectHardware('trezor', 0).catch(() => null)
await metamaskController.forgetDevice('trezor')
const keyrings = await metamaskController.keyringController.getKeyringsByType(
'Trezor Hardware'
@ -376,7 +376,7 @@ describe('MetaMaskController', function () {
sinon.spy(metamaskController.preferencesController, 'setAddresses')
sinon.spy(metamaskController.preferencesController, 'setSelectedAddress')
sinon.spy(metamaskController.preferencesController, 'setAccountLabel')
await metamaskController.connectHardware('trezor', 0, `m/44/0'/0'`).catch((e) => null)
await metamaskController.connectHardware('trezor', 0, `m/44/0'/0'`).catch(() => null)
await metamaskController.unlockHardwareWalletAccount(accountToUnlock, 'trezor', `m/44/0'/0'`)
})
@ -464,7 +464,7 @@ describe('MetaMaskController', function () {
depositAddress = '3EevLFfB4H4XMWQwYCgjLie1qCAGpd2WBc'
depositType = 'ETH'
shapeShiftTxList = metamaskController.shapeshiftController.store.getState().shapeShiftTxList
shapeShiftTxList = metamaskController.shapeshiftController.state.shapeShiftTxList
})
it('creates a shapeshift tx', async function () {
@ -752,12 +752,11 @@ describe('MetaMaskController', function () {
})
it('sets up phishing stream for untrusted communication ', async () => {
await metamaskController.blacklistController.updatePhishingList()
console.log(blacklistJSON.blacklist.includes(phishingUrl))
await metamaskController.phishingController.updatePhishingLists()
const { promise, resolve } = deferredPromise()
streamTest = createThoughStream((chunk, enc, cb) => {
streamTest = createThoughStream((chunk, _, cb) => {
if (chunk.name !== 'phishing') return cb()
assert.equal(chunk.data.hostname, phishingUrl)
resolve()
@ -777,7 +776,7 @@ describe('MetaMaskController', function () {
})
it('sets up controller dnode api for trusted communication', function (done) {
streamTest = createThoughStream((chunk, enc, cb) => {
streamTest = createThoughStream((chunk, _, cb) => {
assert.equal(chunk.name, 'controller')
cb()
done()

@ -92,6 +92,9 @@ describe('Network utils', () => {
}, {
input: 'mainnet',
expected: 'Main Ethereum Network',
}, {
input: 'goerli',
expected: 'Goerli',
},
]

@ -527,14 +527,14 @@ describe('preferences controller', 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: '' }])
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: '' }])
assert.deepEqual(preferencesController.store.getState().frequentRpcListDetail, [{ rpcUrl: 'rpc_url', chainId: 1, ticker: 'ETH', nickname: '', rpcPrefs: {} }])
})
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: '' }])
assert.deepEqual(preferencesController.store.getState().frequentRpcListDetail, [{ rpcUrl: 'rpc_url', chainId: 1, ticker: 'ETH', nickname: '', rpcPrefs: {} }])
preferencesController.removeFromFrequentRpcList('other_rpc_url')
preferencesController.removeFromFrequentRpcList('http://localhost:8545')
preferencesController.removeFromFrequentRpcList('rpc_url')

@ -1,238 +0,0 @@
const assert = require('assert')
const NonceTracker = require('../../../../../app/scripts/controllers/transactions/nonce-tracker')
const MockTxGen = require('../../../../lib/mock-tx-gen')
const providerResultStub = {}
describe('Nonce Tracker', function () {
let nonceTracker, pendingTxs, confirmedTxs
describe('#getNonceLock', function () {
describe('with 3 confirmed and 1 pending', function () {
beforeEach(function () {
const txGen = new MockTxGen()
confirmedTxs = txGen.generate({ status: 'confirmed' }, { count: 3 })
pendingTxs = txGen.generate({ status: 'submitted' }, { count: 1 })
nonceTracker = generateNonceTrackerWith(pendingTxs, confirmedTxs, '0x1')
})
it('should return 4', async function () {
this.timeout(15000)
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926')
assert.equal(nonceLock.nextNonce, '4', `nonce should be 4 got ${nonceLock.nextNonce}`)
await nonceLock.releaseLock()
})
it('should use localNonce if network returns a nonce lower then a confirmed tx in state', async function () {
this.timeout(15000)
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926')
assert.equal(nonceLock.nextNonce, '4', 'nonce should be 4')
await nonceLock.releaseLock()
})
})
describe('sentry issue 476304902', function () {
beforeEach(function () {
const txGen = new MockTxGen()
pendingTxs = txGen.generate({ status: 'submitted' }, {
fromNonce: 3,
count: 29,
})
nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x3')
})
it('should return 9', async function () {
this.timeout(15000)
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926')
assert.equal(nonceLock.nextNonce, '32', `nonce should be 32 got ${nonceLock.nextNonce}`)
await nonceLock.releaseLock()
})
})
describe('issue 3670', function () {
beforeEach(function () {
const txGen = new MockTxGen()
pendingTxs = txGen.generate({ status: 'submitted' }, {
fromNonce: 6,
count: 3,
})
nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x6')
})
it('should return 9', async function () {
this.timeout(15000)
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926')
assert.equal(nonceLock.nextNonce, '9', `nonce should be 9 got ${nonceLock.nextNonce}`)
await nonceLock.releaseLock()
})
})
describe('with no previous txs', function () {
beforeEach(function () {
nonceTracker = generateNonceTrackerWith([], [])
})
it('should return 0', async function () {
this.timeout(15000)
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926')
assert.equal(nonceLock.nextNonce, '0', `nonce should be 0 returned ${nonceLock.nextNonce}`)
await nonceLock.releaseLock()
})
})
describe('with multiple previous txs with same nonce', function () {
beforeEach(function () {
const txGen = new MockTxGen()
confirmedTxs = txGen.generate({ status: 'confirmed' }, { count: 1 })
pendingTxs = txGen.generate({
status: 'submitted',
txParams: { nonce: '0x01' },
}, { count: 5 })
nonceTracker = generateNonceTrackerWith(pendingTxs, confirmedTxs, '0x0')
})
it('should return nonce after those', async function () {
this.timeout(15000)
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926')
assert.equal(nonceLock.nextNonce, '2', `nonce should be 2 got ${nonceLock.nextNonce}`)
await nonceLock.releaseLock()
})
})
describe('when local confirmed count is higher than network nonce', function () {
beforeEach(function () {
const txGen = new MockTxGen()
confirmedTxs = txGen.generate({ status: 'confirmed' }, { count: 3 })
nonceTracker = generateNonceTrackerWith([], confirmedTxs, '0x1')
})
it('should return nonce after those', async function () {
this.timeout(15000)
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926')
assert.equal(nonceLock.nextNonce, '3', `nonce should be 3 got ${nonceLock.nextNonce}`)
await nonceLock.releaseLock()
})
})
describe('when local pending count is higher than other metrics', function () {
beforeEach(function () {
const txGen = new MockTxGen()
pendingTxs = txGen.generate({ status: 'submitted' }, { count: 2 })
nonceTracker = generateNonceTrackerWith(pendingTxs, [])
})
it('should return nonce after those', async function () {
this.timeout(15000)
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926')
assert.equal(nonceLock.nextNonce, '2', `nonce should be 2 got ${nonceLock.nextNonce}`)
await nonceLock.releaseLock()
})
})
describe('when provider nonce is higher than other metrics', function () {
beforeEach(function () {
const txGen = new MockTxGen()
pendingTxs = txGen.generate({ status: 'submitted' }, { count: 2 })
nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x05')
})
it('should return nonce after those', async function () {
this.timeout(15000)
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926')
assert.equal(nonceLock.nextNonce, '5', `nonce should be 5 got ${nonceLock.nextNonce}`)
await nonceLock.releaseLock()
})
})
describe('when there are some pending nonces below the remote one and some over.', function () {
beforeEach(function () {
const txGen = new MockTxGen()
pendingTxs = txGen.generate({ status: 'submitted' }, { count: 5 })
nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x03')
})
it('should return nonce after those', async function () {
this.timeout(15000)
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926')
assert.equal(nonceLock.nextNonce, '5', `nonce should be 5 got ${nonceLock.nextNonce}`)
await nonceLock.releaseLock()
})
})
describe('when there are pending nonces non sequentially over the network nonce.', function () {
beforeEach(function () {
const txGen = new MockTxGen()
txGen.generate({ status: 'submitted' }, { count: 5 })
// 5 over that number
pendingTxs = txGen.generate({ status: 'submitted' }, { count: 5 })
nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x00')
})
it('should return nonce after network nonce', async function () {
this.timeout(15000)
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926')
assert.equal(nonceLock.nextNonce, '0', `nonce should be 0 got ${nonceLock.nextNonce}`)
await nonceLock.releaseLock()
})
})
describe('When all three return different values', function () {
beforeEach(function () {
const txGen = new MockTxGen()
confirmedTxs = txGen.generate({ status: 'confirmed' }, { count: 10 })
pendingTxs = txGen.generate({
status: 'submitted',
nonce: 100,
}, { count: 1 })
// 0x32 is 50 in hex:
nonceTracker = generateNonceTrackerWith(pendingTxs, confirmedTxs, '0x32')
})
it('should return nonce after network nonce', async function () {
this.timeout(15000)
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926')
assert.equal(nonceLock.nextNonce, '50', `nonce should be 50 got ${nonceLock.nextNonce}`)
await nonceLock.releaseLock()
})
})
describe('Faq issue 67', function () {
beforeEach(function () {
const txGen = new MockTxGen()
confirmedTxs = txGen.generate({ status: 'confirmed' }, { count: 64 })
pendingTxs = txGen.generate({
status: 'submitted',
}, { count: 10 })
// 0x40 is 64 in hex:
nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x40')
})
it('should return nonce after network nonce', async function () {
this.timeout(15000)
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926')
assert.equal(nonceLock.nextNonce, '74', `nonce should be 74 got ${nonceLock.nextNonce}`)
await nonceLock.releaseLock()
})
})
})
})
function generateNonceTrackerWith (pending, confirmed, providerStub = '0x0') {
const getPendingTransactions = () => pending
const getConfirmedTransactions = () => confirmed
providerResultStub.result = providerStub
const provider = {
sendAsync: (_, cb) => { cb(undefined, providerResultStub) },
}
const blockTracker = {
getCurrentBlock: () => '0x11b568',
getLatestBlock: async () => '0x11b568',
}
return new NonceTracker({
provider,
blockTracker,
getPendingTransactions,
getConfirmedTransactions,
})
}

@ -58,7 +58,7 @@ describe('PendingTransactionTracker', function () {
}
})
it('should become failed if another tx with the same nonce succeeds', async function () {
it('should emit dropped if another tx with the same nonce succeeds', async function () {
// SETUP
const txGen = new MockTxGen()
@ -84,29 +84,60 @@ describe('PendingTransactionTracker', function () {
// THE EXPECTATION
const spy = sinon.spy()
pendingTxTracker.on('tx:failed', (txId, err) => {
pendingTxTracker.on('tx:dropped', (txId) => {
assert.equal(txId, pending.id, 'should fail the pending tx')
assert.equal(err.name, 'NonceTakenErr', 'should emit a nonce taken error.')
spy(txId, err)
spy(txId)
})
// THE METHOD
await pendingTxTracker._checkPendingTx(pending)
// THE ASSERTION
assert.ok(spy.calledWith(pending.id), 'tx failed should be emitted')
assert.ok(spy.calledWith(pending.id), 'tx dropped should be emitted')
})
})
describe('#_checkPendingTx', function () {
it('should emit \'tx:failed\' if the txMeta does not have a hash', function (done) {
pendingTxTracker.once('tx:failed', (txId, err) => {
pendingTxTracker.once('tx:failed', (txId) => {
assert(txId, txMetaNoHash.id, 'should pass txId')
done()
})
pendingTxTracker._checkPendingTx(txMetaNoHash)
})
it('should emit tx:dropped with the txMetas id only after the second call', function (done) {
txMeta = {
id: 1,
hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb',
status: 'submitted',
txParams: {
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
nonce: '0x1',
value: '0xfffff',
},
history: [{}],
rawTx: '0xf86c808504a817c800827b0d940c62bb85faa3311a998d3aba8098c1235c564966880de0b6b3a7640000802aa08ff665feb887a25d4099e40e11f0fef93ee9608f404bd3f853dd9e84ed3317a6a02ec9d3d1d6e176d4d2593dd760e74ccac753e6a0ea0d00cc9789d0d7ff1f471d',
}
providerResultStub['eth_getTransactionCount'] = '0x02'
providerResultStub['eth_getTransactionByHash'] = {}
pendingTxTracker.once('tx:dropped', (id) => {
if (id === txMeta.id) {
delete providerResultStub['eth_getTransactionCount']
delete providerResultStub['eth_getTransactionByHash']
return done()
} else {
done(new Error('wrong tx Id'))
}
})
pendingTxTracker._checkPendingTx(txMeta).then(() => {
pendingTxTracker._checkPendingTx(txMeta).catch(done)
}).catch(done)
})
it('should should return if query does not return txParams', function () {
providerResultStub.eth_getTransactionByHash = null
pendingTxTracker._checkPendingTx(txMeta)
@ -128,7 +159,7 @@ describe('PendingTransactionTracker', function () {
pendingTxTracker.getPendingTransactions = () => txList
pendingTxTracker._checkPendingTx = (tx) => { tx.resolve(tx) }
Promise.all(txList.map((tx) => tx.processed))
.then((txCompletedList) => done())
.then(() => done())
.catch(done)
pendingTxTracker.updatePendingTxs()
@ -152,7 +183,7 @@ describe('PendingTransactionTracker', function () {
pendingTxTracker.getPendingTransactions = () => txList
pendingTxTracker._resubmitTx = async (tx) => { tx.resolve(tx) }
Promise.all(txList.map((tx) => tx.processed))
.then((txCompletedList) => done())
.then(() => done())
.catch(done)
pendingTxTracker.resubmitPendingTxs(blockNumberStub)
})
@ -178,7 +209,7 @@ describe('PendingTransactionTracker', function () {
throw new Error(knownErrors.pop())
}
Promise.all(txList.map((tx) => tx.processed))
.then((txCompletedList) => done())
.then(() => done())
.catch(done)
pendingTxTracker.resubmitPendingTxs(blockNumberStub)
@ -194,9 +225,9 @@ describe('PendingTransactionTracker', function () {
})
pendingTxTracker.getPendingTransactions = () => txList
pendingTxTracker._resubmitTx = async (tx) => { throw new TypeError('im some real error') }
pendingTxTracker._resubmitTx = async () => { throw new TypeError('im some real error') }
Promise.all(txList.map((tx) => tx.processed))
.then((txCompletedList) => done())
.then(() => done())
.catch(done)
pendingTxTracker.resubmitPendingTxs(blockNumberStub)
@ -283,6 +314,37 @@ describe('PendingTransactionTracker', function () {
})
})
describe('#_checkIftxWasDropped', () => {
const txMeta = {
id: 1,
hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb',
status: 'submitted',
txParams: {
from: '0x1678a085c290ebd122dc42cba69373b5953b831d',
nonce: '0x1',
value: '0xfffff',
},
rawTx: '0xf86c808504a817c800827b0d940c62bb85faa3311a998d3aba8098c1235c564966880de0b6b3a7640000802aa08ff665feb887a25d4099e40e11f0fef93ee9608f404bd3f853dd9e84ed3317a6a02ec9d3d1d6e176d4d2593dd760e74ccac753e6a0ea0d00cc9789d0d7ff1f471d',
}
it('should return false when the nonce is the suggested network nonce', (done) => {
providerResultStub['eth_getTransactionCount'] = '0x01'
providerResultStub['eth_getTransactionByHash'] = {}
pendingTxTracker._checkIftxWasDropped(txMeta).then((dropped) => {
assert(!dropped, 'should be false')
done()
}).catch(done)
})
it('should return true when the network nonce is higher then the txMeta nonce', function (done) {
providerResultStub['eth_getTransactionCount'] = '0x02'
providerResultStub['eth_getTransactionByHash'] = {}
pendingTxTracker._checkIftxWasDropped(txMeta).then((dropped) => {
assert(dropped, 'should be true')
done()
}).catch(done)
})
})
describe('#_checkIfNonceIsTaken', function () {
beforeEach(function () {
const confirmedTxList = [{

@ -4,6 +4,7 @@ const {
ROPSTEN_CODE,
RINKEYBY_CODE,
KOVAN_CODE,
GOERLI_CODE,
} = require('../../../../../app/scripts/controllers/network/enums')
const KeyringController = require('eth-keyring-controller')
@ -27,14 +28,14 @@ describe('Recipient Blacklist Checker', function () {
describe('#checkAccount', function () {
it('does not fail on test networks', function () {
let callCount = 0
const networks = [ROPSTEN_CODE, RINKEYBY_CODE, KOVAN_CODE]
const networks = [ROPSTEN_CODE, RINKEYBY_CODE, KOVAN_CODE, GOERLI_CODE]
for (const networkId in networks) {
publicAccounts.forEach((account) => {
recipientBlackListChecker.checkAccount(networkId, account)
callCount++
})
}
assert.equal(callCount, 30)
assert.equal(callCount, 40)
})
it('fails on mainnet', function () {

@ -8,6 +8,13 @@ const TransactionController = require('../../../../../app/scripts/controllers/tr
const {
TRANSACTION_TYPE_RETRY,
} = require('../../../../../app/scripts/controllers/transactions/enums')
const {
TOKEN_METHOD_APPROVE,
TOKEN_METHOD_TRANSFER,
SEND_ETHER_ACTION_KEY,
DEPLOY_CONTRACT_ACTION_KEY,
CONTRACT_INTERACTION_KEY,
} = require('../../../../../ui/app/helpers/constants/transactions.js')
const { createTestProviderTools, getTestAccounts } = require('../../../../stub/provider')
const noop = () => true
@ -537,6 +544,86 @@ describe('Transaction Controller', function () {
})
})
describe('#_determineTransactionCategory', function () {
it('should return a simple send transactionCategory when to is truthy but data is falsey', async function () {
const result = await txController._determineTransactionCategory({
to: '0xabc',
data: '',
})
assert.deepEqual(result, { transactionCategory: SEND_ETHER_ACTION_KEY, getCodeResponse: undefined })
})
it('should return a token transfer transactionCategory when data is for the respective method call', async function () {
const result = await txController._determineTransactionCategory({
to: '0xabc',
data: '0xa9059cbb0000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C970000000000000000000000000000000000000000000000000000000000000000a',
})
assert.deepEqual(result, { transactionCategory: TOKEN_METHOD_TRANSFER, getCodeResponse: undefined })
})
it('should return a token approve transactionCategory when data is for the respective method call', async function () {
const result = await txController._determineTransactionCategory({
to: '0xabc',
data: '0x095ea7b30000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C9700000000000000000000000000000000000000000000000000000000000000005',
})
assert.deepEqual(result, { transactionCategory: TOKEN_METHOD_APPROVE, getCodeResponse: undefined })
})
it('should return a contract deployment transactionCategory when to is falsey and there is data', async function () {
const result = await txController._determineTransactionCategory({
to: '',
data: '0xabd',
})
assert.deepEqual(result, { transactionCategory: DEPLOY_CONTRACT_ACTION_KEY, getCodeResponse: undefined })
})
it('should return a simple send transactionCategory with a 0x getCodeResponse when there is data and but the to address is not a contract address', async function () {
const result = await txController._determineTransactionCategory({
to: '0x9e673399f795D01116e9A8B2dD2F156705131ee9',
data: '0xabd',
})
assert.deepEqual(result, { transactionCategory: SEND_ETHER_ACTION_KEY, getCodeResponse: '0x' })
})
it('should return a simple send transactionCategory with a null getCodeResponse when to is truthy and there is data and but getCode returns an error', async function () {
const result = await txController._determineTransactionCategory({
to: '0xabc',
data: '0xabd',
})
assert.deepEqual(result, { transactionCategory: SEND_ETHER_ACTION_KEY, getCodeResponse: null })
})
it('should return a contract interaction transactionCategory with the correct getCodeResponse when to is truthy and there is data and it is not a token transaction', async function () {
const _providerResultStub = {
// 1 gwei
eth_gasPrice: '0x0de0b6b3a7640000',
// by default, all accounts are external accounts (not contracts)
eth_getCode: '0xa',
}
const _provider = createTestProviderTools({ scaffold: _providerResultStub }).provider
const _fromAccount = getTestAccounts()[0]
const _blockTrackerStub = new EventEmitter()
_blockTrackerStub.getCurrentBlock = noop
_blockTrackerStub.getLatestBlock = noop
const _txController = new TransactionController({
provider: _provider,
getGasPrice: function () { return '0xee6b2800' },
networkStore: new ObservableStore(currentNetworkId),
txHistoryLimit: 10,
blockTracker: _blockTrackerStub,
signTransaction: (ethTx) => new Promise((resolve) => {
ethTx.sign(_fromAccount.key)
resolve()
}),
})
const result = await _txController._determineTransactionCategory({
to: '0x9e673399f795D01116e9A8B2dD2F156705131ee9',
data: 'abd',
})
assert.deepEqual(result, { transactionCategory: CONTRACT_INTERACTION_KEY, getCodeResponse: '0x0a' })
})
})
describe('#getPendingTransactions', function () {
beforeEach(function () {
txController.txStateManager._saveTxList([

@ -11,7 +11,7 @@ describe('txUtils', function () {
before(function () {
txUtils = new TxUtils(new Proxy({}, {
get: (obj, name) => {
get: () => {
return () => {}
},
}))

@ -29,7 +29,7 @@ describe('Transaction state history helper', function () {
describe('#migrateFromSnapshotsToDiffs', function () {
it('migrates history to diffs and can recover original values', function () {
testVault.data.TransactionController.transactions.forEach((tx, index) => {
testVault.data.TransactionController.transactions.forEach((tx) => {
const newHistory = txStateHistoryHelper.migrateFromSnapshotsToDiffs(tx.history)
newHistory.forEach((newEntry, index) => {
if (index === 0) {

@ -55,7 +55,7 @@ describe('TransactionStateManager', function () {
it('should emit a rejected event to signal the exciton of callback', (done) => {
const tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }
txStateManager.addTx(tx)
const noop = function (err, txId) {
const noop = function (err) {
if (err) {
console.log('Error: ', err)
}

@ -83,7 +83,7 @@ describe('EdgeEncryptor', function () {
edgeEncryptor.encrypt(password, data)
.then(function (encryptedData) {
edgeEncryptor.decrypt('wrong password', encryptedData)
.then(function (decryptedData) {
.then(function () {
assert.fail('could decrypt with wrong password')
done()
})

@ -61,7 +61,7 @@ describe('Migrator', () => {
const migrator = new Migrator({ migrations: [{ version: 1, migrate: async () => { throw new Error('test') } } ] })
migrator.on('error', () => done())
migrator.migrateData({ meta: {version: 0} })
.then((migratedData) => {
.then(() => {
}).catch(done)
})

@ -44,7 +44,7 @@ describe('Actions', () => {
showUnapprovedTx: noop,
showUnconfirmedMessage: noop,
encryptor: {
encrypt: function (password, object) {
encrypt: function (_, object) {
this.object = object
return Promise.resolve('mock-encrypted')
},
@ -103,7 +103,7 @@ describe('Actions', () => {
submitPasswordSpy = sinon.stub(background, 'submitPassword')
submitPasswordSpy.callsFake((password, callback) => {
submitPasswordSpy.callsFake((_, callback) => {
callback(new Error('error in submitPassword'))
})
@ -235,7 +235,7 @@ describe('Actions', () => {
createNewVaultAndRestoreSpy = sinon.stub(background, 'createNewVaultAndRestore')
createNewVaultAndRestoreSpy.callsFake((password, seed, callback) => {
createNewVaultAndRestoreSpy.callsFake((_, __, callback) => {
callback(new Error('error'))
})
@ -279,7 +279,7 @@ describe('Actions', () => {
]
createNewVaultAndKeychainSpy = sinon.stub(background, 'createNewVaultAndKeychain')
createNewVaultAndKeychainSpy.callsFake((password, callback) => {
createNewVaultAndKeychainSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
@ -342,7 +342,7 @@ describe('Actions', () => {
]
submitPasswordSpy = sinon.stub(background, 'submitPassword')
submitPasswordSpy.callsFake((password, callback) => {
submitPasswordSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
@ -414,7 +414,7 @@ describe('Actions', () => {
it('displays warning error message when submitPassword in background errors', () => {
submitPasswordSpy = sinon.stub(background, 'submitPassword')
submitPasswordSpy.callsFake((password, callback) => {
submitPasswordSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
@ -483,7 +483,7 @@ describe('Actions', () => {
{ type: 'DISPLAY_WARNING', value: 'error' },
]
removeAccountSpy = sinon.stub(background, 'removeAccount')
removeAccountSpy.callsFake((address, callback) => {
removeAccountSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
@ -522,7 +522,7 @@ describe('Actions', () => {
{ type: 'DISPLAY_WARNING', value: 'error' },
]
addNewKeyringSpy.callsFake((type, opts, callback) => {
addNewKeyringSpy.callsFake((_, __, callback) => {
callback(new Error('error'))
})
@ -611,7 +611,7 @@ describe('Actions', () => {
]
importAccountWithStrategySpy = sinon.stub(background, 'importAccountWithStrategy')
importAccountWithStrategySpy.callsFake((strategy, args, callback) => {
importAccountWithStrategySpy.callsFake((_, __, callback) => {
callback(new Error('error'))
})
@ -668,7 +668,7 @@ describe('Actions', () => {
{ type: 'HIDE_LOADING_INDICATION' },
{ type: 'DISPLAY_WARNING', value: 'error' },
]
setCurrentCurrencySpy.callsFake((currencyCode, callback) => {
setCurrentCurrencySpy.callsFake((_, callback) => {
callback(new Error('error'))
})
@ -720,7 +720,7 @@ describe('Actions', () => {
]
signMessageSpy = sinon.stub(background, 'signMessage')
signMessageSpy.callsFake((msgData, callback) => {
signMessageSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
@ -775,7 +775,7 @@ describe('Actions', () => {
]
signPersonalMessageSpy = sinon.stub(background, 'signPersonalMessage')
signPersonalMessageSpy.callsFake((msgData, callback) => {
signPersonalMessageSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
@ -812,7 +812,7 @@ describe('Actions', () => {
{ type: 'DISPLAY_WARNING', value: 'error' },
{ type: 'SHOW_CONF_TX_PAGE', transForward: true, id: undefined },
]
sendTransactionSpy.callsFake((txData, callback) => {
sendTransactionSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
@ -906,7 +906,7 @@ describe('Actions', () => {
{ type: 'DISPLAY_WARNING', value: 'error' },
]
setSelectedAddressSpy.callsFake((address, callback) => {
setSelectedAddressSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
@ -941,7 +941,7 @@ describe('Actions', () => {
{ type: 'HIDE_LOADING_INDICATION' },
{ type: 'DISPLAY_WARNING', value: 'error' },
]
setSelectedAddressSpy.callsFake((address, callback) => {
setSelectedAddressSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
@ -980,7 +980,7 @@ describe('Actions', () => {
{ type: 'UPDATE_TOKENS', newTokens: undefined },
]
addTokenSpy.callsFake((address, symbol, decimals, image, callback) => {
addTokenSpy.callsFake((_, __, ___, ____, callback) => {
callback(new Error('error'))
})
@ -1020,7 +1020,7 @@ describe('Actions', () => {
{ type: 'UPDATE_TOKENS', newTokens: undefined },
]
removeTokenSpy.callsFake((address, callback) => {
removeTokenSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
@ -1054,7 +1054,7 @@ describe('Actions', () => {
{ type: 'DISPLAY_WARNING', value: 'Had a problem changing networks!' },
]
setProviderTypeSpy.callsFake((type, callback) => {
setProviderTypeSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
@ -1087,7 +1087,7 @@ describe('Actions', () => {
{ type: 'DISPLAY_WARNING', value: 'Had a problem changing networks!' },
]
setRpcTargetSpy.callsFake((newRpc, chainId, ticker, nickname, callback) => {
setRpcTargetSpy.callsFake((_, __, ___, ____, callback) => {
callback(new Error('error'))
})
@ -1134,7 +1134,7 @@ describe('Actions', () => {
exportAccountSpy = sinon.spy(background, 'exportAccount')
return store.dispatch(actions.exportAccount(password, '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'))
.then((result) => {
.then(() => {
assert(submitPasswordSpy.calledOnce)
assert(exportAccountSpy.calledOnce)
assert.deepEqual(store.getActions(), expectedActions)
@ -1150,7 +1150,7 @@ describe('Actions', () => {
]
submitPasswordSpy = sinon.stub(background, 'submitPassword')
submitPasswordSpy.callsFake((password, callback) => {
submitPasswordSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
@ -1169,7 +1169,7 @@ describe('Actions', () => {
]
exportAccountSpy = sinon.stub(background, 'exportAccount')
exportAccountSpy.callsFake((address, callback) => {
exportAccountSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
@ -1196,7 +1196,6 @@ describe('Actions', () => {
describe('#pairUpdate', () => {
beforeEach(() => {
nock('https://shapeshift.io')
.defaultReplyHeaders({ 'access-control-allow-origin': '*' })
.get('/marketinfo/btc_eth')
@ -1206,10 +1205,6 @@ describe('Actions', () => {
.defaultReplyHeaders({ 'access-control-allow-origin': '*' })
.get('/coins')
.reply(200)
})
afterEach(() => {
nock.restore()
})
it('', () => {
@ -1251,7 +1246,7 @@ describe('Actions', () => {
{ type: 'DISPLAY_WARNING', value: 'error' },
]
setFeatureFlagSpy.callsFake((feature, activated, callback) => {
setFeatureFlagSpy.callsFake((_, __, callback) => {
callback(new Error('error'))
})
@ -1305,7 +1300,7 @@ describe('Actions', () => {
]
getTransactionCountSpy = sinon.stub(global.ethQuery, 'getTransactionCount')
getTransactionCountSpy.callsFake((address, callback) => {
getTransactionCountSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
@ -1343,7 +1338,7 @@ describe('Actions', () => {
{ type: 'SET_USE_BLOCKIE', value: undefined },
]
setUseBlockieSpy.callsFake((val, callback) => {
setUseBlockieSpy.callsFake((_, callback) => {
callback(new Error('error'))
})
@ -1390,7 +1385,7 @@ describe('Actions', () => {
{ type: 'DISPLAY_WARNING', value: 'error' },
]
setCurrentLocaleSpy = sinon.stub(background, 'setCurrentLocale')
setCurrentLocaleSpy.callsFake((key, callback) => {
setCurrentLocaleSpy.callsFake((_, callback) => {
callback(new Error('error'))
})

@ -23,4 +23,8 @@ describe('Etherscan Network Prefix', () => {
assert.equal(etherscanNetworkPrefix(42), 'kovan.')
})
it('returs goerli as prefix for networkId of 5', () => {
assert.equal(etherscanNetworkPrefix(5), 'goerli.')
})
})

@ -0,0 +1,105 @@
<html>
<head>
<title>Web3 Test Dapp</title>
</head>
<body>
<div style="display: flex; flex-flow: column;">
<div style="display: flex; font-size: 1.25rem;">hexaNumberMethods</div>
<div style="display: flex;">
<button id="eth_blockNumber" class="hexaNumberMethods">eth_blockNumber</button>
<button id="eth_gasPrice" class="hexaNumberMethods">eth_gasPrice</button>
<button id="eth_newBlockFilter" class="hexaNumberMethods">eth_newBlockFilter</button>
<button id="eth_newPendingTransactionFilter" class="hexaNumberMethods">
eth_newPendingTransactionFilter
</button>
<button id="eth_getUncleCountByBlockHash" class="hexaNumberMethods">
eth_getUncleCountByBlockHash
</button>
<button id="eth_getBlockTransactionCountByHash" class="hexaNumberMethods">
getBlockTransactionCountByHash
</button>
</div>
<div style="display: flex ;">
<button id="eth_getTransactionCount" class="hexaNumberMethods">eth_getTransactionCount</button>
<button id="eth_getBalance" class="hexaNumberMethods">eth_getBalance</button>
<button id="eth_estimateGas" class="hexaNumberMethods">eth_estimateGas</button>
</div>
<div style="display: flex ;">
<button id="eth_getUncleCountByBlockNumber" class="hexaNumberMethods">
eth_getUncleCountByBlockNumber
</button>
<button id='eth_getBlockTransactionCountByNumber' class="hexaNumberMethods">
eth_getBlockTransactionCountByNumber
</button>
<button id="eth_protocolVersion" class="hexaNumberMethods">eth_protocolVersion</button>
<button id="eth_getCode" class="hexaNumberMethods">eth_getCode</button>
</div>
</div>
<div style="display: flex; flex-flow: column;">
<div style="display: flex; font-size: 1.25rem;">booleanMethods</div>
<div style="display: flex ;">
<button id="eth_uninstallFilter" class = 'booleanMethods'>eth_uninstallFilter</button>
<button id="eth_mining" class = 'booleanMethods'>eth_mining</button>
<button id="eth_syncing" class = 'booleanMethods'>eth_syncing</button>
</div>
</div>
<div style="display: flex; flex-flow: column;">
<div style="display: flex; font-size: 1.25rem;" >transactionMethods</div>
<div style="display: flex ;">
<button id="eth_getTransactionByHash" class='transactionMethods'>eth_getTransactionByHash</button>
<button id="eth_getTransactionByBlockHashAndIndex" class = 'transactionMethods'>
eth_getTransactionByBlockHashAndIndex
</button>
<button id="eth_getTransactionByBlockNumberAndIndex" class="transactionMethods">
eth_getTransactionByBlockNumberAndIndex
</button>
</div>
</div>
<div style="display: flex; flex-flow: column;">
<div style="display: flex; font-size: 1.25rem;">blockMethods</div>
<div style="display: flex ;">
<button id="eth_getUncleByBlockHashAndIndex" class="blockMethods">
eth_getUncleByBlockHashAndIndex
</button>
<button id="eth_getBlockByHash" class="blockMethods">eth_getBlockByHash</button>
</div>
<div style="display: flex ;">
<button id="eth_getBlockByNumber" class="blockMethods">eth_getBlockByNumber</button>
</div>
</div>
<div style="display: flex; flex-flow: column;">
<div style="display: flex; font-size: 1.25rem;">Methods</div>
<div style="display: flex ;">
<button id="eth_call" class = 'methods'>eth_call</button>
<button id="eth_getStorageAt" class="methods">eth_getStorageAt</button>
<button id="eth_getTransactionReceipt" class="methods">
eth_getTransactionReceipt
</button>
</div>
</div>
<div style="display: flex; flex-flow: column;">
<div id='results'></div>
</div>
</div>
<script src="schema.js"></script>
<script src="web3.js"></script>
</body>
</html>

@ -0,0 +1,209 @@
/* eslint no-unused-vars: 0 */
var params = {
// diffrent params used in the methods
param: [],
blockHashParams: '0xb3b20624f8f0f86eb50dd04688409e5cea4bd02d700bf6e79e9384d47d6a5a35',
filterParams: ['0xfe704947a3cd3ca12541458a4321c869'],
transactionHashParams: [
'0xbb3a336e3f823ec18197f1e13ee875700f08f03e2cab75f0d0b118dabb44cba0',
],
blockHashAndIndexParams: [
'0xb3b20624f8f0f86eb50dd04688409e5cea4bd02d700bf6e79e9384d47d6a5a35',
'0x0',
],
uncleByBlockNumberAndIndexParams: ['0x29c', '0x0'],
blockParameterParams: '0x5bad55',
data: '0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675',
addressParams: '0xc94770007dda54cF92009BFF0dE90c06F603a09f',
getStorageAtParams: [
'0x295a70b2de5e3953354a6a8344e616ed314d7251',
'0x6661e9d6d8b923d5bbaab1b96e1dd51ff6ea2a93520fdc9eb75d059238b8c5e9',
'0x65a8db',
],
getCodeParams: ['0x06012c8cf97bead5deae237070f9587f8e7a266d', '0x65a8db'],
estimateTransaction: {
from: '0xb60e8dd61c5d32be8058bb8eb970870f07233155',
to: '0xd46e8dd67c5d32be8058bb8eb970870f07244567',
gas: '0x76c0',
gasPrice: '0x9184e72a000',
value: '0x9184e72a',
data: '0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675',
},
filterGetLogs: [{'blockHash': '0x7c5a35e9cb3e8ae0e221ab470abae9d446c3a5626ce6689fc777dcffcab52c70', 'topics': ['0x241ea03ca20251805084d27d4440371c34a0b85ff108f6bb5611248f73818b80']}],
block: {
__required: [],
number: 'Q',
hash: 'D32',
parentHash: 'D32',
nonce: 'D',
sha3Uncles: 'D',
logsBloom: 'D',
transactionsRoot: 'D',
stateRoot: 'D',
receiptsRoot: 'D',
miner: 'D',
difficulty: 'Q',
totalDifficulty: 'Q',
extraData: 'D',
size: 'Q',
gasLimit: 'Q',
gasUsed: 'Q',
timestamp: 'Q',
transactions: ['DATA|Transaction'],
uncles: ['D'],
},
transaction: {
__required: [],
hash: 'D32',
nonce: 'Q',
blockHash: 'D32',
blockNumber: 'Q',
transactionIndex: 'Q',
from: 'D20',
to: 'D20',
value: 'Q',
gasPrice: 'Q',
gas: 'Q',
input: 'D',
},
receipt: {
__required: [],
transactionHash: 'D32',
transactionIndex: 'Q',
blockHash: 'D32',
blockNumber: 'Q',
cumulativeGasUsed: 'Q',
gasUsed: 'Q',
contractAddress: 'D20',
logs: ['FilterChange'],
},
filterChange: {
__required: [],
removed: 'B',
logIndex: 'Q',
transactionIndex: 'Q',
transactionHash: 'D32',
blockHash: 'D32',
blockNumber: 'Q',
address: 'D20',
data: 'Array|DATA',
topics: ['D'],
},
}
var methods = {
hexaNumberMethods: {
// these are the methods which have output in the form of hexa decimal numbers
eth_blockNumber: ['eth_blockNumber', params.param, 'Q'],
eth_gasPrice: ['eth_gasPrice', params.param, 'Q'],
eth_newBlockFilter: ['eth_newBlockFilter', params.param, 'Q'],
eth_newPendingTransactionFilter: [
'eth_newPendingTransactionFilter',
params.param,
'Q',
],
eth_getUncleCountByBlockHash: [
'eth_getUncleCountByBlockHash',
[params.blockHashParams],
'Q',
1,
],
eth_getBlockTransactionCountByHash: [
'eth_getBlockTransactionCountByHash',
[params.blockHashParams],
'Q',
1,
],
eth_getTransactionCount: [
'eth_getTransactionCount',
[params.addressParams, params.blockParameterParams],
'Q',
1,
2,
],
eth_getBalance: ['eth_getBalance', [params.addressParams, 'latest'], 'Q', 1, 2],
eth_estimateGas: ['eth_estimateGas', [params.estimateTransaction], 'Q', 1],
eth_getUncleCountByBlockNumber: [
'eth_getUncleCountByBlockNumber',
[params.blockParameterParams],
'Q',
1,
],
eth_getBlockTransactionCountByNumber: [
'eth_getBlockTransactionCountByNumber',
['latest'],
'Q',
1,
],
eth_protocolVersion: ['eth_protocolVersion', params.param, 'S'],
eth_getCode: ['eth_getCode', params.getCodeParams, 'D', 1, 2],
},
booleanMethods: {
// these are the methods which have output in the form of boolean
eth_uninstallFilter: ['eth_uninstallFilter', params.filterParams, 'B', 1],
eth_mining: ['eth_mining', params.param, 'B'],
eth_syncing: ['eth_syncing', params.param, 'B|EthSyncing'],
},
transactionMethods: {
// these are the methods which have output in the form of transaction object
eth_getTransactionByHash: [
'eth_getTransactionByHash',
params.transactionHashParams,
params.transaction,
1,
],
eth_getTransactionByBlockHashAndIndex: [
'eth_getTransactionByBlockHashAndIndex',
params.blockHashAndIndexParams,
params.transaction,
2,
],
eth_getTransactionByBlockNumberAndIndex: [
'eth_getTransactionByBlockNumberAndIndex',
[params.blockParameterParams, '0x0'],
params.transaction,
2,
],
},
blockMethods: {
// these are the methods which have output in the form of a block
eth_getUncleByBlockNumberAndIndex: [
'eth_getUncleByBlockNumberAndIndex',
params.uncleByBlockNumberAndIndexParams,
params.block,
2,
],
eth_getBlockByHash: [
'eth_getBlockByHash',
[params.params, false],
params.block,
2,
],
eth_getBlockByNumber: [
'eth_getBlockByNumber',
[params.blockParameterParams, false],
params.block,
2,
],
},
methods: {
// these are the methods which have output in the form of bytes data
eth_call: ['eth_call', [params.estimateTransaction, 'latest'], 'D', 1, 2],
eth_getStorageAt: ['eth_getStorageAt', params.getStorageAtParams, 'D', 2, 2],
eth_getTransactionReceipt: [
'eth_getTransactionReceipt',
params.transactionHashParams,
params.receipt,
1,
],
},
}

@ -0,0 +1,34 @@
/* eslint no-undef: 0 */
var json = methods
web3.currentProvider.enable().then(() => {
Object.keys(json).forEach(methodGroupKey => {
console.log(methodGroupKey)
const methodGroup = json[methodGroupKey]
console.log(methodGroup)
Object.keys(methodGroup).forEach(methodKey => {
const methodButton = document.getElementById(methodKey)
methodButton.addEventListener('click', function () {
window.ethereum.sendAsync({
method: methodKey,
params: methodGroup[methodKey][1],
}, function (err, result) {
if (err) {
console.log(err)
console.log(methodKey)
} else {
document.getElementById('results').innerHTML = JSON.stringify(result)
}
})
})
})
})
})

@ -69,18 +69,9 @@ AccountPanel.prototype.render = function () {
)
}
function balanceOrFaucetingIndication (account, isFauceting) {
// Temporarily deactivating isFauceting indication
// because it shows fauceting for empty restored accounts.
if (/* isFauceting*/ false) {
return {
key: 'Account is auto-funding.',
value: 'Please wait.',
}
} else {
return {
key: 'BALANCE',
value: formatBalance(account.balance),
}
function balanceOrFaucetingIndication (account) {
return {
key: 'BALANCE',
value: formatBalance(account.balance),
}
}

@ -17,10 +17,7 @@
}
&__button {
font-size: 0.75rem;
@extend %small-link;
margin: 1rem;
text-transform: uppercase;
color: $curious-blue;
cursor: pointer;
}
}

@ -48,7 +48,6 @@
&__contents {
display: flex;
justify-content: space-between;
flex-flow: row nowrap;
width: 100%;
@ -74,17 +73,33 @@
flex-direction: row;
align-items: center;
cursor: pointer;
flex: 0 0 auto;
}
&__account-menu-container {
display: flex;
flex-flow: row nowrap;
align-items: center;
flex: 1 1 auto;
width: 0;
flex-flow: row nowrap;
justify-content: flex-end;
}
&__network-component-wrapper {
display: flex;
flex-direction: row;
align-items: center;
flex: 1 0 auto;
width: 0;
justify-content: flex-end;
.network-component.pointer {
max-width: 200px;
}
.network-indicator {
width: 100%;
}
}
}

@ -116,7 +116,7 @@ BnAsDecimalInput.prototype.render = function () {
)
}
BnAsDecimalInput.prototype.setValid = function (message) {
BnAsDecimalInput.prototype.setValid = function () {
this.setState({ invalid: null })
}

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

Loading…
Cancel
Save