aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorpldespaigne <pl.despaigne@gmail.com>2019-05-31 00:22:55 +0800
committerpldespaigne <pl.despaigne@gmail.com>2019-05-31 00:22:55 +0800
commit9a658ee53d1f75ce07c33581ac1189fa8c4fd173 (patch)
treeea92ef1971ffaa72c29bf16904906bc1841654c7
parent9b87aaae1907eb04ca0a4055b5bb2c863e56aa39 (diff)
parent681f3f67b89b64fc837df1103198b641c7e7b2d6 (diff)
downloadtangerine-wallet-browser-9a658ee53d1f75ce07c33581ac1189fa8c4fd173.tar.gz
tangerine-wallet-browser-9a658ee53d1f75ce07c33581ac1189fa8c4fd173.tar.zst
tangerine-wallet-browser-9a658ee53d1f75ce07c33581ac1189fa8c4fd173.zip
merge
-rw-r--r--.editorconfig15
-rw-r--r--.eslintrc2
-rw-r--r--CHANGELOG.md64
-rw-r--r--app/_locales/en/messages.json61
-rw-r--r--app/_locales/it/messages.json234
-rw-r--r--app/manifest.json2
-rw-r--r--app/scripts/contentscript.js214
-rw-r--r--app/scripts/controllers/app-state.js73
-rw-r--r--app/scripts/controllers/balance.js2
-rw-r--r--app/scripts/controllers/blacklist.js136
-rw-r--r--app/scripts/controllers/network/createBlockTracker.js19
-rw-r--r--app/scripts/controllers/network/createInfuraClient.js10
-rw-r--r--app/scripts/controllers/network/createJsonRpcClient.js6
-rw-r--r--app/scripts/controllers/network/createLocalhostClient.js6
-rw-r--r--app/scripts/controllers/network/enums.js6
-rw-r--r--app/scripts/controllers/network/network.js19
-rw-r--r--app/scripts/controllers/network/util.js5
-rw-r--r--app/scripts/controllers/preferences.js46
-rw-r--r--app/scripts/controllers/provider-approval.js124
-rw-r--r--app/scripts/controllers/recent-blocks.js3
-rw-r--r--app/scripts/controllers/shapeshift.js180
-rw-r--r--app/scripts/controllers/token-rates.js18
-rw-r--r--app/scripts/controllers/transactions/index.js59
-rw-r--r--app/scripts/controllers/transactions/nonce-tracker.js161
-rw-r--r--app/scripts/controllers/transactions/pending-tx-tracker.js53
-rw-r--r--app/scripts/controllers/transactions/tx-gas-utils.js16
-rw-r--r--app/scripts/controllers/transactions/tx-state-manager.js4
-rw-r--r--app/scripts/controllers/user-actions.js17
-rw-r--r--app/scripts/createStandardProvider.js29
-rw-r--r--app/scripts/inpage.js128
-rw-r--r--app/scripts/lib/backend-metametrics.js26
-rw-r--r--app/scripts/lib/buy-eth-url.js4
-rw-r--r--app/scripts/lib/createDnodeRemoteGetter.js16
-rw-r--r--app/scripts/lib/createProviderMiddleware.js16
-rw-r--r--app/scripts/lib/message-manager.js2
-rw-r--r--app/scripts/lib/personal-message-manager.js2
-rw-r--r--app/scripts/metamask-controller.js184
-rw-r--r--app/scripts/migrations/024.js2
-rw-r--r--app/scripts/migrations/025.js2
-rw-r--r--app/scripts/platforms/extension.js14
-rw-r--r--development/backGroundConnectionModifiers.js6
-rwxr-xr-xdevelopment/metamaskbot-build-announce.js16
-rw-r--r--development/states/conf-tx.json3
-rw-r--r--development/states/confirm-new-ui.json4
-rw-r--r--development/states/confirm-sig-requests.json4
-rw-r--r--development/states/currency-localization.json4
-rw-r--r--development/states/navigate-txs.json5
-rw-r--r--development/states/pending-tx.json5
-rw-r--r--development/states/send-edit.json4
-rw-r--r--development/states/send-new-ui.json4
-rw-r--r--development/states/send.json3
-rw-r--r--development/states/tx-list-items.json4
-rw-r--r--docs/creating-metrics-events.md71
-rw-r--r--docs/design-system.md5
-rw-r--r--docs/limited_site_access.md5
-rw-r--r--docs/porting_to_new_environment.md4
-rw-r--r--docs/publishing.md9
-rw-r--r--gentests.js6
-rw-r--r--gulpfile.js2
-rw-r--r--package-lock.json450
-rw-r--r--package.json17
-rw-r--r--test/data/v17-long-history.json5
-rw-r--r--test/e2e/beta/contract-test/contract.js37
-rw-r--r--test/e2e/beta/contract-test/index.html2
-rw-r--r--test/e2e/beta/from-import-beta-ui.spec.js55
-rw-r--r--test/e2e/beta/metamask-beta-responsive-ui.spec.js2
-rw-r--r--test/e2e/beta/metamask-beta-ui.spec.js167
-rwxr-xr-xtest/e2e/beta/run-web3.sh9
-rw-r--r--test/e2e/beta/web3.spec.js365
-rw-r--r--test/integration/lib/confirm-sig-requests.js21
-rw-r--r--test/integration/lib/currency-localization.js13
-rw-r--r--test/integration/lib/send-new-ui.js42
-rw-r--r--test/integration/lib/tx-list-items.js13
-rw-r--r--test/lib/mock-encryptor.js6
-rw-r--r--test/lib/util.js2
-rw-r--r--test/unit/actions/tx_test.js4
-rw-r--r--test/unit/app/controllers/blacklist-controller-test.js56
-rw-r--r--test/unit/app/controllers/currency-controller-test.js2
-rw-r--r--test/unit/app/controllers/infura-controller-test.js12
-rw-r--r--test/unit/app/controllers/metamask-controller-test.js25
-rw-r--r--test/unit/app/controllers/network-contoller-test.js3
-rw-r--r--test/unit/app/controllers/preferences-controller-test.js6
-rw-r--r--test/unit/app/controllers/transactions/nonce-tracker-test.js238
-rw-r--r--test/unit/app/controllers/transactions/pending-tx-test.js84
-rw-r--r--test/unit/app/controllers/transactions/recipient-blacklist-checker-test.js5
-rw-r--r--test/unit/app/controllers/transactions/tx-controller-test.js87
-rw-r--r--test/unit/app/controllers/transactions/tx-gas-util-test.js2
-rw-r--r--test/unit/app/controllers/transactions/tx-state-history-helper-test.js2
-rw-r--r--test/unit/app/controllers/transactions/tx-state-manager-test.js2
-rw-r--r--test/unit/app/edge-encryptor-test.js2
-rw-r--r--test/unit/migrations/migrator-test.js2
-rw-r--r--test/unit/ui/app/actions.spec.js57
-rw-r--r--test/unit/ui/etherscan-prefix-for-network.spec.js4
-rw-r--r--test/web3/index.html105
-rw-r--r--test/web3/schema.js209
-rw-r--r--test/web3/web3.js34
-rw-r--r--ui/app/components/app/account-panel.js17
-rw-r--r--ui/app/components/app/add-token-button/index.scss5
-rw-r--r--ui/app/components/app/app-header/index.scss17
-rw-r--r--ui/app/components/app/bn-as-decimal-input.js2
-rw-r--r--ui/app/components/app/coinbase-form.js69
-rw-r--r--ui/app/components/app/customize-gas-modal/index.js8
-rw-r--r--ui/app/components/app/dropdowns/account-details-dropdown.js17
-rw-r--r--ui/app/components/app/dropdowns/network-dropdown.js36
-rw-r--r--ui/app/components/app/dropdowns/tests/network-dropdown.test.js14
-rw-r--r--ui/app/components/app/ens-input.js18
-rw-r--r--ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js2
-rw-r--r--ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.container.js4
-rw-r--r--ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js3
-rw-r--r--ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js2
-rw-r--r--ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js74
-rw-r--r--ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js12
-rw-r--r--ui/app/components/app/gas-customization/gas-price-button-group/gas-price-button-group.component.js2
-rw-r--r--ui/app/components/app/gas-customization/gas-price-button-group/index.scss17
-rw-r--r--ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.utils.js6
-rw-r--r--ui/app/components/app/gas-customization/gas-price-chart/tests/gas-price-chart.component.test.js2
-rw-r--r--ui/app/components/app/loading-network-screen/loading-network-screen.component.js4
-rw-r--r--ui/app/components/app/modal/modal.component.js2
-rw-r--r--ui/app/components/app/modal/tests/modal.component.test.js4
-rw-r--r--ui/app/components/app/modals/account-details-modal.js16
-rw-r--r--ui/app/components/app/modals/customize-gas/customize-gas.component.js4
-rw-r--r--ui/app/components/app/modals/deposit-ether-modal.js6
-rw-r--r--ui/app/components/app/modals/edit-account-name-modal.js2
-rw-r--r--ui/app/components/app/modals/export-private-key-modal.js8
-rw-r--r--ui/app/components/app/modals/hide-token-confirmation-modal.js4
-rw-r--r--ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.container.js2
-rw-r--r--ui/app/components/app/modals/notification-modal.js4
-rw-r--r--ui/app/components/app/modals/qr-scanner/qr-scanner.component.js2
-rw-r--r--ui/app/components/app/modals/reject-transactions/reject-transactions.container.js2
-rw-r--r--ui/app/components/app/network-display/index.scss8
-rw-r--r--ui/app/components/app/network-display/network-display.component.js2
-rw-r--r--ui/app/components/app/network.js32
-rw-r--r--ui/app/components/app/provider-page-container/provider-page-container.component.js13
-rw-r--r--ui/app/components/app/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js65
-rw-r--r--ui/app/components/app/shapeshift-form.js2
-rw-r--r--ui/app/components/app/signature-request.js2
-rw-r--r--ui/app/components/app/token-cell.js2
-rw-r--r--ui/app/components/app/transaction-list-item-details/transaction-list-item-details.component.js15
-rw-r--r--ui/app/components/app/transaction-list-item/transaction-list-item.component.js3
-rw-r--r--ui/app/components/app/transaction-list-item/transaction-list-item.container.js7
-rw-r--r--ui/app/components/app/transaction-view-balance/transaction-view-balance.component.js6
-rw-r--r--ui/app/components/app/user-preferenced-currency-display/tests/user-preferenced-currency-display.container.test.js2
-rw-r--r--ui/app/components/app/user-preferenced-currency-display/user-preferenced-currency-display.container.js2
-rw-r--r--ui/app/components/app/wallet-view.js2
-rw-r--r--ui/app/components/ui/account-dropdown-mini/account-dropdown-mini.component.js2
-rw-r--r--ui/app/components/ui/account-dropdown-mini/tests/account-dropdown-mini.component.test.js2
-rw-r--r--ui/app/components/ui/alert/index.js4
-rw-r--r--ui/app/components/ui/button/button.component.js9
-rw-r--r--ui/app/components/ui/button/button.stories.js47
-rw-r--r--ui/app/components/ui/button/buttons.scss247
-rw-r--r--ui/app/components/ui/currency-display/currency-display.component.js2
-rw-r--r--ui/app/components/ui/currency-display/tests/currency-display.container.test.js2
-rw-r--r--ui/app/components/ui/currency-input/currency-input.component.js4
-rw-r--r--ui/app/components/ui/currency-input/currency-input.container.js3
-rw-r--r--ui/app/components/ui/currency-input/tests/currency-input.container.test.js18
-rw-r--r--ui/app/components/ui/page-container/index.scss8
-rw-r--r--ui/app/components/ui/page-container/page-container-footer/page-container-footer.component.js2
-rw-r--r--ui/app/components/ui/text-field/text-field.component.js6
-rw-r--r--ui/app/components/ui/token-input/tests/token-input.container.test.js2
-rw-r--r--ui/app/components/ui/unit-input/index.scss20
-rw-r--r--ui/app/components/ui/unit-input/unit-input.component.js14
-rw-r--r--ui/app/css/itcss/components/buttons.scss230
-rw-r--r--ui/app/css/itcss/components/index.scss2
-rw-r--r--ui/app/css/itcss/components/modal.scss2
-rw-r--r--ui/app/css/itcss/components/network.scss35
-rw-r--r--ui/app/css/itcss/components/send.scss205
-rw-r--r--ui/app/css/itcss/generic/index.scss1
-rw-r--r--ui/app/css/itcss/settings/typography.scss37
-rw-r--r--ui/app/css/itcss/settings/variables.scss18
-rw-r--r--ui/app/ducks/app/app.js12
-rw-r--r--ui/app/ducks/confirm-transaction/confirm-transaction.duck.js2
-rw-r--r--ui/app/ducks/confirm-transaction/confirm-transaction.duck.test.js2
-rw-r--r--ui/app/ducks/gas/gas-duck.test.js4
-rw-r--r--ui/app/ducks/metamask/metamask.js19
-rw-r--r--ui/app/helpers/constants/common.js1
-rw-r--r--ui/app/helpers/constants/routes.js2
-rw-r--r--ui/app/helpers/higher-order-components/i18n-provider.js12
-rw-r--r--ui/app/helpers/higher-order-components/metametrics/metametrics.provider.js2
-rw-r--r--ui/app/helpers/higher-order-components/with-modal-props/tests/with-modal-props.test.js2
-rw-r--r--ui/app/helpers/utils/conversion-util.js2
-rw-r--r--ui/app/helpers/utils/conversions.util.js11
-rw-r--r--ui/app/helpers/utils/metametrics.util.js61
-rw-r--r--ui/app/helpers/utils/transactions.util.js16
-rw-r--r--ui/app/helpers/utils/util.js9
-rw-r--r--ui/app/pages/add-token/token-list/token-list-placeholder/index.scss3
-rw-r--r--ui/app/pages/confirm-add-suggested-token/confirm-add-suggested-token.component.js2
-rw-r--r--ui/app/pages/confirm-add-suggested-token/confirm-add-suggested-token.container.js2
-rw-r--r--ui/app/pages/confirm-add-token/confirm-add-token.component.js2
-rw-r--r--ui/app/pages/confirm-deploy-contract/confirm-deploy-contract.component.js2
-rw-r--r--ui/app/pages/confirm-send-ether/confirm-send-ether.component.js2
-rw-r--r--ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js42
-rw-r--r--ui/app/pages/confirm-transaction-base/confirm-transaction-base.container.js4
-rw-r--r--ui/app/pages/create-account/connect-hardware/account-list.js6
-rw-r--r--ui/app/pages/create-account/connect-hardware/connect-screen.js6
-rw-r--r--ui/app/pages/create-account/connect-hardware/index.js12
-rw-r--r--ui/app/pages/create-account/import-account/json.js2
-rw-r--r--ui/app/pages/create-account/import-account/private-key.js2
-rw-r--r--ui/app/pages/create-account/import-account/seed.js2
-rw-r--r--ui/app/pages/create-account/new-account.js2
-rw-r--r--ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js20
-rw-r--r--ui/app/pages/first-time-flow/create-password/new-account/new-account.component.js2
-rw-r--r--ui/app/pages/first-time-flow/create-password/unique-image/unique-image.component.js2
-rw-r--r--ui/app/pages/first-time-flow/end-of-flow/end-of-flow.component.js2
-rw-r--r--ui/app/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js4
-rw-r--r--ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js193
-rw-r--r--ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.state.js41
-rw-r--r--ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/draggable-seed.component.js126
-rw-r--r--ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.scss93
-rw-r--r--ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js2
-rw-r--r--ui/app/pages/first-time-flow/seed-phrase/seed-phrase.component.js76
-rw-r--r--ui/app/pages/first-time-flow/seed-phrase/tests/confirm-seed-phrase-component.test.js169
-rw-r--r--ui/app/pages/first-time-flow/select-action/select-action.component.js2
-rw-r--r--ui/app/pages/first-time-flow/welcome/welcome.component.js2
-rw-r--r--ui/app/pages/home/home.component.js19
-rw-r--r--ui/app/pages/home/home.container.js2
-rw-r--r--ui/app/pages/keychains/reveal-seed.js2
-rw-r--r--ui/app/pages/mobile-sync/index.js2
-rw-r--r--ui/app/pages/provider-approval/provider-approval.component.js10
-rw-r--r--ui/app/pages/provider-approval/provider-approval.container.js6
-rw-r--r--ui/app/pages/routes/index.js34
-rw-r--r--ui/app/pages/send/README.md (renamed from ui/app/components/app/send/README.md)0
-rw-r--r--ui/app/pages/send/account-list-item/account-list-item-README.md (renamed from ui/app/components/app/send/account-list-item/account-list-item-README.md)0
-rw-r--r--ui/app/pages/send/account-list-item/account-list-item.component.js (renamed from ui/app/components/app/send/account-list-item/account-list-item.component.js)10
-rw-r--r--ui/app/pages/send/account-list-item/account-list-item.container.js (renamed from ui/app/components/app/send/account-list-item/account-list-item.container.js)2
-rw-r--r--ui/app/pages/send/account-list-item/index.js (renamed from ui/app/components/app/send/account-list-item/index.js)0
-rw-r--r--ui/app/pages/send/account-list-item/tests/account-list-item-component.test.js (renamed from ui/app/components/app/send/account-list-item/tests/account-list-item-component.test.js)6
-rw-r--r--ui/app/pages/send/account-list-item/tests/account-list-item-container.test.js (renamed from ui/app/components/app/send/account-list-item/tests/account-list-item-container.test.js)4
-rw-r--r--ui/app/pages/send/index.js (renamed from ui/app/components/app/send/index.js)0
-rw-r--r--ui/app/pages/send/send-content/index.js (renamed from ui/app/components/app/send/send-content/index.js)0
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/README.md (renamed from ui/app/components/app/send/send-content/send-amount-row/README.md)0
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js75
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.container.js (renamed from ui/app/components/app/send/send-content/send-amount-row/amount-max-button/amount-max-button.container.js)9
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.selectors.js (renamed from ui/app/components/app/send/send-content/send-amount-row/amount-max-button/amount-max-button.selectors.js)0
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js (renamed from ui/app/components/app/send/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js)2
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/amount-max-button/index.js (renamed from ui/app/components/app/send/send-content/send-amount-row/amount-max-button/index.js)0
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js (renamed from ui/app/components/app/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js)14
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js (renamed from ui/app/components/app/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js)6
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-selectors.test.js (renamed from ui/app/components/app/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-selectors.test.js)0
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-utils.test.js (renamed from ui/app/components/app/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-utils.test.js)0
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/index.js (renamed from ui/app/components/app/send/send-content/send-amount-row/index.js)0
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/send-amount-row.component.js (renamed from ui/app/components/app/send/send-content/send-amount-row/send-amount-row.component.js)6
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/send-amount-row.container.js (renamed from ui/app/components/app/send/send-content/send-amount-row/send-amount-row.container.js)4
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/send-amount-row.scss (renamed from ui/app/components/app/send/send-content/send-amount-row/send-amount-row.scss)0
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/send-amount-row.selectors.js (renamed from ui/app/components/app/send/send-content/send-amount-row/send-amount-row.selectors.js)0
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/tests/send-amount-row-component.test.js (renamed from ui/app/components/app/send/send-content/send-amount-row/tests/send-amount-row-component.test.js)2
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/tests/send-amount-row-container.test.js (renamed from ui/app/components/app/send/send-content/send-amount-row/tests/send-amount-row-container.test.js)4
-rw-r--r--ui/app/pages/send/send-content/send-amount-row/tests/send-amount-row-selectors.test.js (renamed from ui/app/components/app/send/send-content/send-amount-row/tests/send-amount-row-selectors.test.js)0
-rw-r--r--ui/app/pages/send/send-content/send-asset-row/index.js1
-rw-r--r--ui/app/pages/send/send-content/send-asset-row/send-asset-row.component.js152
-rw-r--r--ui/app/pages/send/send-content/send-asset-row/send-asset-row.container.js21
-rw-r--r--ui/app/pages/send/send-content/send-content.component.js (renamed from ui/app/components/app/send/send-content/send-content.component.js)4
-rw-r--r--ui/app/pages/send/send-content/send-dropdown-list/index.js (renamed from ui/app/components/app/send/send-content/send-dropdown-list/index.js)0
-rw-r--r--ui/app/pages/send/send-content/send-dropdown-list/send-dropdown-list.component.js (renamed from ui/app/components/app/send/send-content/send-dropdown-list/send-dropdown-list.component.js)0
-rw-r--r--ui/app/pages/send/send-content/send-dropdown-list/tests/send-dropdown-list-component.test.js (renamed from ui/app/components/app/send/send-content/send-dropdown-list/tests/send-dropdown-list-component.test.js)0
-rw-r--r--ui/app/pages/send/send-content/send-from-row/index.js (renamed from ui/app/components/app/send/send-content/send-from-row/index.js)0
-rw-r--r--ui/app/pages/send/send-content/send-from-row/send-from-row.component.js (renamed from ui/app/components/app/send/send-content/send-from-row/send-from-row.component.js)0
-rw-r--r--ui/app/pages/send/send-content/send-from-row/send-from-row.container.js (renamed from ui/app/components/app/send/send-content/send-from-row/send-from-row.container.js)0
-rw-r--r--ui/app/pages/send/send-content/send-from-row/send-from-row.selectors.js (renamed from ui/app/components/app/send/send-content/send-from-row/send-from-row.selectors.js)0
-rw-r--r--ui/app/pages/send/send-content/send-from-row/tests/send-from-row-component.test.js (renamed from ui/app/components/app/send/send-content/send-from-row/tests/send-from-row-component.test.js)0
-rw-r--r--ui/app/pages/send/send-content/send-from-row/tests/send-from-row-container.test.js (renamed from ui/app/components/app/send/send-content/send-from-row/tests/send-from-row-container.test.js)0
-rw-r--r--ui/app/pages/send/send-content/send-from-row/tests/send-from-row-selectors.test.js (renamed from ui/app/components/app/send/send-content/send-from-row/tests/send-from-row-selectors.test.js)0
-rw-r--r--ui/app/pages/send/send-content/send-gas-row/README.md (renamed from ui/app/components/app/send/send-content/send-gas-row/README.md)0
-rw-r--r--ui/app/pages/send/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js (renamed from ui/app/components/app/send/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js)4
-rw-r--r--ui/app/pages/send/send-content/send-gas-row/gas-fee-display/index.js (renamed from ui/app/components/app/send/send-content/send-gas-row/gas-fee-display/index.js)0
-rw-r--r--ui/app/pages/send/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js (renamed from ui/app/components/app/send/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js)2
-rw-r--r--ui/app/pages/send/send-content/send-gas-row/index.js (renamed from ui/app/components/app/send/send-content/send-gas-row/index.js)0
-rw-r--r--ui/app/pages/send/send-content/send-gas-row/send-gas-row.component.js (renamed from ui/app/components/app/send/send-content/send-gas-row/send-gas-row.component.js)45
-rw-r--r--ui/app/pages/send/send-content/send-gas-row/send-gas-row.container.js (renamed from ui/app/components/app/send/send-content/send-gas-row/send-gas-row.container.js)26
-rw-r--r--ui/app/pages/send/send-content/send-gas-row/send-gas-row.scss (renamed from ui/app/components/app/send/send-content/send-gas-row/send-gas-row.scss)0
-rw-r--r--ui/app/pages/send/send-content/send-gas-row/send-gas-row.selectors.js (renamed from ui/app/components/app/send/send-content/send-gas-row/send-gas-row.selectors.js)0
-rw-r--r--ui/app/pages/send/send-content/send-gas-row/tests/send-gas-row-component.test.js (renamed from ui/app/components/app/send/send-content/send-gas-row/tests/send-gas-row-component.test.js)2
-rw-r--r--ui/app/pages/send/send-content/send-gas-row/tests/send-gas-row-container.test.js (renamed from ui/app/components/app/send/send-content/send-gas-row/tests/send-gas-row-container.test.js)19
-rw-r--r--ui/app/pages/send/send-content/send-gas-row/tests/send-gas-row-selectors.test.js (renamed from ui/app/components/app/send/send-content/send-gas-row/tests/send-gas-row-selectors.test.js)0
-rw-r--r--ui/app/pages/send/send-content/send-hex-data-row/index.js (renamed from ui/app/components/app/send/send-content/send-hex-data-row/index.js)0
-rw-r--r--ui/app/pages/send/send-content/send-hex-data-row/send-hex-data-row.component.js (renamed from ui/app/components/app/send/send-content/send-hex-data-row/send-hex-data-row.component.js)0
-rw-r--r--ui/app/pages/send/send-content/send-hex-data-row/send-hex-data-row.container.js (renamed from ui/app/components/app/send/send-content/send-hex-data-row/send-hex-data-row.container.js)2
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/index.js (renamed from ui/app/components/app/send/send-content/send-row-wrapper/index.js)0
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/index.js (renamed from ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/index.js)0
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message-README.md (renamed from ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message-README.md)0
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js (renamed from ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js)3
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js (renamed from ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js)0
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.scss (renamed from ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.scss)0
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-component.test.js (renamed from ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-component.test.js)0
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-container.test.js (renamed from ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-container.test.js)2
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/index.js (renamed from ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/index.js)0
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.component.js (renamed from ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.component.js)0
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.container.js (renamed from ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.container.js)0
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.scss (renamed from ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.scss)0
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-component.test.js (renamed from ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-component.test.js)0
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-container.test.js (renamed from ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-container.test.js)2
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/send-row-wrapper-README.md (renamed from ui/app/components/app/send/send-content/send-row-wrapper/send-row-wrapper-README.md)0
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/send-row-wrapper.component.js (renamed from ui/app/components/app/send/send-content/send-row-wrapper/send-row-wrapper.component.js)46
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/send-row-wrapper.scss (renamed from ui/app/components/app/send/send-content/send-row-wrapper/send-row-wrapper.scss)0
-rw-r--r--ui/app/pages/send/send-content/send-row-wrapper/tests/send-row-wrapper-component.test.js (renamed from ui/app/components/app/send/send-content/send-row-wrapper/tests/send-row-wrapper-component.test.js)0
-rw-r--r--ui/app/pages/send/send-content/send-to-row/index.js (renamed from ui/app/components/app/send/send-content/send-to-row/index.js)0
-rw-r--r--ui/app/pages/send/send-content/send-to-row/send-to-row-README.md (renamed from ui/app/components/app/send/send-content/send-to-row/send-to-row-README.md)0
-rw-r--r--ui/app/pages/send/send-content/send-to-row/send-to-row.component.js (renamed from ui/app/components/app/send/send-content/send-to-row/send-to-row.component.js)2
-rw-r--r--ui/app/pages/send/send-content/send-to-row/send-to-row.container.js (renamed from ui/app/components/app/send/send-content/send-to-row/send-to-row.container.js)4
-rw-r--r--ui/app/pages/send/send-content/send-to-row/send-to-row.selectors.js (renamed from ui/app/components/app/send/send-content/send-to-row/send-to-row.selectors.js)0
-rw-r--r--ui/app/pages/send/send-content/send-to-row/send-to-row.utils.js (renamed from ui/app/components/app/send/send-content/send-to-row/send-to-row.utils.js)9
-rw-r--r--ui/app/pages/send/send-content/send-to-row/tests/send-to-row-component.test.js (renamed from ui/app/components/app/send/send-content/send-to-row/tests/send-to-row-component.test.js)2
-rw-r--r--ui/app/pages/send/send-content/send-to-row/tests/send-to-row-container.test.js (renamed from ui/app/components/app/send/send-content/send-to-row/tests/send-to-row-container.test.js)4
-rw-r--r--ui/app/pages/send/send-content/send-to-row/tests/send-to-row-selectors.test.js (renamed from ui/app/components/app/send/send-content/send-to-row/tests/send-to-row-selectors.test.js)0
-rw-r--r--ui/app/pages/send/send-content/send-to-row/tests/send-to-row-utils.test.js (renamed from ui/app/components/app/send/send-content/send-to-row/tests/send-to-row-utils.test.js)12
-rw-r--r--ui/app/pages/send/send-content/tests/send-content-component.test.js (renamed from ui/app/components/app/send/send-content/tests/send-content-component.test.js)17
-rw-r--r--ui/app/pages/send/send-footer/README.md (renamed from ui/app/components/app/send/send-footer/README.md)0
-rw-r--r--ui/app/pages/send/send-footer/index.js (renamed from ui/app/components/app/send/send-footer/index.js)0
-rw-r--r--ui/app/pages/send/send-footer/send-footer.component.js (renamed from ui/app/components/app/send/send-footer/send-footer.component.js)9
-rw-r--r--ui/app/pages/send/send-footer/send-footer.container.js (renamed from ui/app/components/app/send/send-footer/send-footer.container.js)14
-rw-r--r--ui/app/pages/send/send-footer/send-footer.scss (renamed from ui/app/components/app/send/send-footer/send-footer.scss)0
-rw-r--r--ui/app/pages/send/send-footer/send-footer.selectors.js (renamed from ui/app/components/app/send/send-footer/send-footer.selectors.js)0
-rw-r--r--ui/app/pages/send/send-footer/send-footer.utils.js (renamed from ui/app/components/app/send/send-footer/send-footer.utils.js)1
-rw-r--r--ui/app/pages/send/send-footer/tests/send-footer-component.test.js (renamed from ui/app/components/app/send/send-footer/tests/send-footer-component.test.js)4
-rw-r--r--ui/app/pages/send/send-footer/tests/send-footer-container.test.js (renamed from ui/app/components/app/send/send-footer/tests/send-footer-container.test.js)7
-rw-r--r--ui/app/pages/send/send-footer/tests/send-footer-selectors.test.js (renamed from ui/app/components/app/send/send-footer/tests/send-footer-selectors.test.js)0
-rw-r--r--ui/app/pages/send/send-footer/tests/send-footer-utils.test.js (renamed from ui/app/components/app/send/send-footer/tests/send-footer-utils.test.js)1
-rw-r--r--ui/app/pages/send/send-header/README.md (renamed from ui/app/components/app/send/send-header/README.md)0
-rw-r--r--ui/app/pages/send/send-header/index.js (renamed from ui/app/components/app/send/send-header/index.js)0
-rw-r--r--ui/app/pages/send/send-header/send-header.component.js (renamed from ui/app/components/app/send/send-header/send-header.component.js)5
-rw-r--r--ui/app/pages/send/send-header/send-header.container.js (renamed from ui/app/components/app/send/send-header/send-header.container.js)2
-rw-r--r--ui/app/pages/send/send-header/send-header.selectors.js (renamed from ui/app/components/app/send/send-header/send-header.selectors.js)0
-rw-r--r--ui/app/pages/send/send-header/tests/send-header-component.test.js (renamed from ui/app/components/app/send/send-header/tests/send-header-component.test.js)6
-rw-r--r--ui/app/pages/send/send-header/tests/send-header-container.test.js (renamed from ui/app/components/app/send/send-header/tests/send-header-container.test.js)2
-rw-r--r--ui/app/pages/send/send-header/tests/send-header-selectors.test.js (renamed from ui/app/components/app/send/send-header/tests/send-header-selectors.test.js)0
-rw-r--r--ui/app/pages/send/send.component.js (renamed from ui/app/components/app/send/send.component.js)38
-rw-r--r--ui/app/pages/send/send.constants.js (renamed from ui/app/components/app/send/send.constants.js)2
-rw-r--r--ui/app/pages/send/send.container.js (renamed from ui/app/components/app/send/send.container.js)13
-rw-r--r--ui/app/pages/send/send.scss (renamed from ui/app/components/app/send/send.scss)0
-rw-r--r--ui/app/pages/send/send.selectors.js (renamed from ui/app/components/app/send/send.selectors.js)20
-rw-r--r--ui/app/pages/send/send.utils.js (renamed from ui/app/components/app/send/send.utils.js)4
-rw-r--r--ui/app/pages/send/tests/send-component.test.js (renamed from ui/app/components/app/send/tests/send-component.test.js)2
-rw-r--r--ui/app/pages/send/tests/send-container.test.js (renamed from ui/app/components/app/send/tests/send-container.test.js)11
-rw-r--r--ui/app/pages/send/tests/send-selectors-test-data.js (renamed from ui/app/components/app/send/tests/send-selectors-test-data.js)0
-rw-r--r--ui/app/pages/send/tests/send-selectors.test.js (renamed from ui/app/components/app/send/tests/send-selectors.test.js)9
-rw-r--r--ui/app/pages/send/tests/send-utils.test.js (renamed from ui/app/components/app/send/tests/send-utils.test.js)10
-rw-r--r--ui/app/pages/send/to-autocomplete.component.js (renamed from ui/app/components/app/send/to-autocomplete.component.js)0
-rw-r--r--ui/app/pages/send/to-autocomplete/index.js (renamed from ui/app/components/app/send/to-autocomplete/index.js)0
-rw-r--r--ui/app/pages/send/to-autocomplete/to-autocomplete.js (renamed from ui/app/components/app/send/to-autocomplete/to-autocomplete.js)27
-rw-r--r--ui/app/pages/settings/advanced-tab/advanced-tab.component.js53
-rw-r--r--ui/app/pages/settings/advanced-tab/advanced-tab.container.js11
-rw-r--r--ui/app/pages/settings/advanced-tab/tests/advanced-tab-component.test.js44
-rw-r--r--ui/app/pages/settings/advanced-tab/tests/advanced-tab-container.test.js46
-rw-r--r--ui/app/pages/settings/index.scss60
-rw-r--r--ui/app/pages/settings/info-tab/index.scss2
-rw-r--r--ui/app/pages/settings/networks-tab/index.js1
-rw-r--r--ui/app/pages/settings/networks-tab/index.scss200
-rw-r--r--ui/app/pages/settings/networks-tab/network-form/index.js1
-rw-r--r--ui/app/pages/settings/networks-tab/network-form/network-form.component.js225
-rw-r--r--ui/app/pages/settings/networks-tab/networks-tab.component.js214
-rw-r--r--ui/app/pages/settings/networks-tab/networks-tab.constants.js50
-rw-r--r--ui/app/pages/settings/networks-tab/networks-tab.container.js77
-rw-r--r--ui/app/pages/settings/security-tab/security-tab.component.js6
-rw-r--r--ui/app/pages/settings/settings-tab/index.scss20
-rw-r--r--ui/app/pages/settings/settings.component.js28
-rw-r--r--ui/app/selectors/custom-gas.js2
-rw-r--r--ui/app/selectors/selectors.js29
-rw-r--r--ui/app/store/actions.js124
-rw-r--r--ui/example.js2
-rw-r--r--ui/lib/account-link.js9
-rw-r--r--ui/lib/etherscan-prefix-for-network.js3
-rw-r--r--ui/lib/test-timeout.js2
361 files changed, 6170 insertions, 2571 deletions
diff --git a/.editorconfig b/.editorconfig
index 609f684a0..c6c8b3621 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -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
diff --git a/.eslintrc b/.eslintrc
index ecf59c68c..53033b753 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -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,
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 98ed6ac69..875ad2bef 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json
index 154925d1a..41ba7ef7a 100644
--- a/app/_locales/en/messages.json
+++ b/app/_locales/en/messages.json
@@ -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"
},
diff --git a/app/_locales/it/messages.json b/app/_locales/it/messages.json
index 9e0f1f06d..09ba045b9 100644
--- a/app/_locales/it/messages.json
+++ b/app/_locales/it/messages.json
@@ -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"
}
diff --git a/app/manifest.json b/app/manifest.json
index 941842636..8471fffac 100644
--- a/app/manifest.json
+++ b/app/manifest.json
@@ -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__",
diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js
index 68b6117e5..0c55ae39f 100644
--- a/app/scripts/contentscript.js
+++ b/app/scripts/contentscript.js
@@ -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()
}
/**
@@ -47,148 +44,107 @@ 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 }))
+}
diff --git a/app/scripts/controllers/app-state.js b/app/scripts/controllers/app-state.js
new file mode 100644
index 000000000..9533fd458
--- /dev/null
+++ b/app/scripts/controllers/app-state.js
@@ -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
+
diff --git a/app/scripts/controllers/balance.js b/app/scripts/controllers/balance.js
index 465751e61..b227d5d0a 100644
--- a/app/scripts/controllers/balance.js
+++ b/app/scripts/controllers/balance.js
@@ -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':
diff --git a/app/scripts/controllers/blacklist.js b/app/scripts/controllers/blacklist.js
deleted file mode 100644
index e55b09d03..000000000
--- a/app/scripts/controllers/blacklist.js
+++ /dev/null
@@ -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
diff --git a/app/scripts/controllers/network/createBlockTracker.js b/app/scripts/controllers/network/createBlockTracker.js
deleted file mode 100644
index 6573b18a1..000000000
--- a/app/scripts/controllers/network/createBlockTracker.js
+++ /dev/null
@@ -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
-}
diff --git a/app/scripts/controllers/network/createInfuraClient.js b/app/scripts/controllers/network/createInfuraClient.js
index 884b94db3..0a6e9ecb0 100644
--- a/app/scripts/controllers/network/createInfuraClient.js
+++ b/app/scripts/controllers/network/createInfuraClient.js
@@ -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}"`)
}
diff --git a/app/scripts/controllers/network/createJsonRpcClient.js b/app/scripts/controllers/network/createJsonRpcClient.js
index 369dcd299..a8cbf2aaf 100644
--- a/app/scripts/controllers/network/createJsonRpcClient.js
+++ b/app/scripts/controllers/network/createJsonRpcClient.js
@@ -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 }),
diff --git a/app/scripts/controllers/network/createLocalhostClient.js b/app/scripts/controllers/network/createLocalhostClient.js
index 36593dc70..09b1d3c1c 100644
--- a/app/scripts/controllers/network/createLocalhostClient.js
+++ b/app/scripts/controllers/network/createLocalhostClient.js
@@ -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 }),
diff --git a/app/scripts/controllers/network/enums.js b/app/scripts/controllers/network/enums.js
index 3190eb37c..2f7025392 100644
--- a/app/scripts/controllers/network/enums.js
+++ b/app/scripts/controllers/network/enums.js
@@ -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,
}
diff --git a/app/scripts/controllers/network/network.js b/app/scripts/controllers/network/network.js
index 47432c1e2..2c68e4378 100644
--- a/app/scripts/controllers/network/network.js
+++ b/app/scripts/controllers/network/network.js
@@ -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,
diff --git a/app/scripts/controllers/network/util.js b/app/scripts/controllers/network/util.js
index 261dae721..b0afccd7e 100644
--- a/app/scripts/controllers/network/util.js
+++ b/app/scripts/controllers/network/util.js
@@ -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]
diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js
index 737411890..acf952bb1 100644
--- a/app/scripts/controllers/preferences.js
+++ b/app/scripts/controllers/preferences.js
@@ -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.
diff --git a/app/scripts/controllers/provider-approval.js b/app/scripts/controllers/provider-approval.js
index 2c9182b52..06c499780 100644
--- a/app/scripts/controllers/provider-approval.js
+++ b/app/scripts/controllers/provider-approval.js
@@ -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
diff --git a/app/scripts/controllers/recent-blocks.js b/app/scripts/controllers/recent-blocks.js
index 982ad2aa4..a2b5d1bae 100644
--- a/app/scripts/controllers/recent-blocks.js
+++ b/app/scripts/controllers/recent-blocks.js
@@ -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 {
diff --git a/app/scripts/controllers/shapeshift.js b/app/scripts/controllers/shapeshift.js
deleted file mode 100644
index b2a1462c2..000000000
--- a/app/scripts/controllers/shapeshift.js
+++ /dev/null
@@ -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
diff --git a/app/scripts/controllers/token-rates.js b/app/scripts/controllers/token-rates.js
index 867d36433..6b6265dba 100644
--- a/app/scripts/controllers/token-rates.js
+++ b/app/scripts/controllers/token-rates.js
@@ -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)
diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js
index 2ce736beb..1ae925835 100644
--- a/app/scripts/controllers/transactions/index.js
+++ b/app/scripts/controllers/transactions/index.js
@@ -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
@@ -556,6 +570,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
diff --git a/app/scripts/controllers/transactions/nonce-tracker.js b/app/scripts/controllers/transactions/nonce-tracker.js
deleted file mode 100644
index 421036368..000000000
--- a/app/scripts/controllers/transactions/nonce-tracker.js
+++ /dev/null
@@ -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
diff --git a/app/scripts/controllers/transactions/pending-tx-tracker.js b/app/scripts/controllers/transactions/pending-tx-tracker.js
index 4bf40b1db..bc11f6633 100644
--- a/app/scripts/controllers/transactions/pending-tx-tracker.js
+++ b/app/scripts/controllers/transactions/pending-tx-tracker.js
@@ -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
diff --git a/app/scripts/controllers/transactions/tx-gas-utils.js b/app/scripts/controllers/transactions/tx-gas-utils.js
index 765551167..287fb6f44 100644
--- a/app/scripts/controllers/transactions/tx-gas-utils.js
+++ b/app/scripts/controllers/transactions/tx-gas-utils.js
@@ -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
}
diff --git a/app/scripts/controllers/transactions/tx-state-manager.js b/app/scripts/controllers/transactions/tx-state-manager.js
index 420191d9c..1a2cb5dee 100644
--- a/app/scripts/controllers/transactions/tx-state-manager.js
+++ b/app/scripts/controllers/transactions/tx-state-manager.js
@@ -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
diff --git a/app/scripts/controllers/user-actions.js b/app/scripts/controllers/user-actions.js
deleted file mode 100644
index f777054b8..000000000
--- a/app/scripts/controllers/user-actions.js
+++ /dev/null
@@ -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
diff --git a/app/scripts/createStandardProvider.js b/app/scripts/createStandardProvider.js
index a5f9c5d03..2059b9b3a 100644
--- a/app/scripts/createStandardProvider.js
+++ b/app/scripts/createStandardProvider.js
@@ -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)
})
diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js
index 68394d1ae..a4fb552f1 100644
--- a/app/scripts/inpage.js
+++ b/app/scripts/inpage.js
@@ -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...
diff --git a/app/scripts/lib/backend-metametrics.js b/app/scripts/lib/backend-metametrics.js
new file mode 100644
index 000000000..e3c163c1a
--- /dev/null
+++ b/app/scripts/lib/backend-metametrics.js
@@ -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
diff --git a/app/scripts/lib/buy-eth-url.js b/app/scripts/lib/buy-eth-url.js
index fbe6c6c9e..5cae83a9f 100644
--- a/app/scripts/lib/buy-eth-url.js
+++ b/app/scripts/lib/buy-eth-url.js
@@ -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}"`)
}
diff --git a/app/scripts/lib/createDnodeRemoteGetter.js b/app/scripts/lib/createDnodeRemoteGetter.js
new file mode 100644
index 000000000..b70d218f3
--- /dev/null
+++ b/app/scripts/lib/createDnodeRemoteGetter.js
@@ -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
+}
diff --git a/app/scripts/lib/createProviderMiddleware.js b/app/scripts/lib/createProviderMiddleware.js
deleted file mode 100644
index 8a939ba4e..000000000
--- a/app/scripts/lib/createProviderMiddleware.js
+++ /dev/null
@@ -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()
- })
- }
-}
diff --git a/app/scripts/lib/message-manager.js b/app/scripts/lib/message-manager.js
index e86629590..ac41de523 100644
--- a/app/scripts/lib/message-manager.js
+++ b/app/scripts/lib/message-manager.js
@@ -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: {},
diff --git a/app/scripts/lib/personal-message-manager.js b/app/scripts/lib/personal-message-manager.js
index fdb94f5ec..7c13e521a 100644
--- a/app/scripts/lib/personal-message-manager.js
+++ b/app/scripts/lib/personal-message-manager.js
@@ -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: {},
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index 4108ed4c0..242028c92 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -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,12 +1449,18 @@ 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)
}
@@ -1413,6 +1468,38 @@ module.exports = class MetamaskController extends EventEmitter {
}
/**
+ * 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
* @return {Promise<void>}
@@ -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()
}
}
diff --git a/app/scripts/migrations/024.js b/app/scripts/migrations/024.js
index d0b276a79..6239bab13 100644
--- a/app/scripts/migrations/024.js
+++ b/app/scripts/migrations/024.js
@@ -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 &&
diff --git a/app/scripts/migrations/025.js b/app/scripts/migrations/025.js
index fc3b20a44..fd4faa782 100644
--- a/app/scripts/migrations/025.js
+++ b/app/scripts/migrations/025.js
@@ -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),
diff --git a/app/scripts/platforms/extension.js b/app/scripts/platforms/extension.js
index 099b0d7ea..0c2d222b8 100644
--- a/app/scripts/platforms/extension.js
+++ b/app/scripts/platforms/extension.js
@@ -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()
diff --git a/development/backGroundConnectionModifiers.js b/development/backGroundConnectionModifiers.js
index aee68854b..cf1a723d0 100644
--- a/development/backGroundConnectionModifiers.js
+++ b/development/backGroundConnectionModifiers.js
@@ -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,
diff --git a/development/metamaskbot-build-announce.js b/development/metamaskbot-build-announce.js
index 07adbcb5d..b254c8a90 100755
--- a/development/metamaskbot-build-announce.js
+++ b/development/metamaskbot-build-announce.js
@@ -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`
diff --git a/development/states/conf-tx.json b/development/states/conf-tx.json
index d47b26fd4..7b278f331 100644
--- a/development/states/conf-tx.json
+++ b/development/states/conf-tx.json
@@ -192,7 +192,8 @@
"type": "testnet"
},
"shapeShiftTxList": [],
- "lostAccounts": []
+ "lostAccounts": [],
+ "frequentRpcListDetail": []
},
"appState": {
"menuOpen": false,
diff --git a/development/states/confirm-new-ui.json b/development/states/confirm-new-ui.json
index 4310ed5b7..c9340fc8f 100644
--- a/development/states/confirm-new-ui.json
+++ b/development/states/confirm-new-ui.json
@@ -133,7 +133,9 @@
"preferences": {
"useNativeCurrencyAsPrimaryCurrency": true,
"showFiatInTestnets": true
- }
+ },
+ "completedUiMigration": true,
+ "frequentRpcListDetail": []
},
"appState": {
"menuOpen": false,
diff --git a/development/states/confirm-sig-requests.json b/development/states/confirm-sig-requests.json
index aa3e8dfdf..d531b2ef7 100644
--- a/development/states/confirm-sig-requests.json
+++ b/development/states/confirm-sig-requests.json
@@ -156,7 +156,9 @@
"currentLocale": "en",
"preferences": {
"useNativeCurrencyAsPrimaryCurrency": true
- }
+ },
+ "completedUiMigration": true,
+ "frequentRpcListDetail": []
},
"appState": {
"menuOpen": false,
diff --git a/development/states/currency-localization.json b/development/states/currency-localization.json
index 8288b3020..a9a37ecd0 100644
--- a/development/states/currency-localization.json
+++ b/development/states/currency-localization.json
@@ -115,7 +115,9 @@
"preferences": {
"useNativeCurrencyAsPrimaryCurrency": true,
"showFiatInTestnets": true
- }
+ },
+ "completedUiMigration": true,
+ "frequentRpcListDetail": []
},
"appState": {
"menuOpen": false,
diff --git a/development/states/navigate-txs.json b/development/states/navigate-txs.json
index 23255abed..584a754f0 100644
--- a/development/states/navigate-txs.json
+++ b/development/states/navigate-txs.json
@@ -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
}
-} \ No newline at end of file
+}
diff --git a/development/states/pending-tx.json b/development/states/pending-tx.json
index b4a6565fa..28c1751bd 100644
--- a/development/states/pending-tx.json
+++ b/development/states/pending-tx.json
@@ -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"
-} \ No newline at end of file
+}
diff --git a/development/states/send-edit.json b/development/states/send-edit.json
index fda7d1a31..7c7e8f097 100644
--- a/development/states/send-edit.json
+++ b/development/states/send-edit.json
@@ -137,7 +137,9 @@
"preferences": {
"useNativeCurrencyAsPrimaryCurrency": true,
"showFiatInTestnets": true
- }
+ },
+ "completedUiMigration": true,
+ "frequentRpcListDetail": []
},
"appState": {
"menuOpen": false,
diff --git a/development/states/send-new-ui.json b/development/states/send-new-ui.json
index b8a3ff128..75982f318 100644
--- a/development/states/send-new-ui.json
+++ b/development/states/send-new-ui.json
@@ -116,7 +116,9 @@
"preferences": {
"useNativeCurrencyAsPrimaryCurrency": true,
"showFiatInTestnets": true
- }
+ },
+ "completedUiMigration": true,
+ "frequentRpcListDetail": []
},
"appState": {
"menuOpen": false,
diff --git a/development/states/send.json b/development/states/send.json
index 8ae385564..c71516edc 100644
--- a/development/states/send.json
+++ b/development/states/send.json
@@ -87,7 +87,8 @@
"type": "testnet"
},
"shapeShiftTxList": [],
- "lostAccounts": []
+ "lostAccounts": [],
+ "frequentRpcListDetail": []
},
"appState": {
"menuOpen": false,
diff --git a/development/states/tx-list-items.json b/development/states/tx-list-items.json
index d4e3f3860..4190ee149 100644
--- a/development/states/tx-list-items.json
+++ b/development/states/tx-list-items.json
@@ -1058,7 +1058,9 @@
"currentLocale": "en",
"preferences": {
"useNativeCurrencyAsPrimaryCurrency": true
- }
+ },
+ "completedUiMigration": true,
+ "frequentRpcListDetail": []
},
"appState": {
"menuOpen": false,
diff --git a/docs/creating-metrics-events.md b/docs/creating-metrics-events.md
new file mode 100644
index 000000000..7e7f2d77d
--- /dev/null
+++ b/docs/creating-metrics-events.md
@@ -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
diff --git a/docs/design-system.md b/docs/design-system.md
new file mode 100644
index 000000000..fa088bdbf
--- /dev/null
+++ b/docs/design-system.md
@@ -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)
diff --git a/docs/limited_site_access.md b/docs/limited_site_access.md
new file mode 100644
index 000000000..f703d5c7e
--- /dev/null
+++ b/docs/limited_site_access.md
@@ -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.
diff --git a/docs/porting_to_new_environment.md b/docs/porting_to_new_environment.md
index d901f2b78..f7a2ac8b7 100644
--- a/docs/porting_to_new_environment.md
+++ b/docs/porting_to_new_environment.md
@@ -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!
diff --git a/docs/publishing.md b/docs/publishing.md
index 132c28d9b..392e20955 100644
--- a/docs/publishing.md
+++ b/docs/publishing.md
@@ -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
diff --git a/gentests.js b/gentests.js
index 9c591e98c..a84c2079d 100644
--- a/gentests.js
+++ b/gentests.js
@@ -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 '
diff --git a/gulpfile.js b/gulpfile.js
index caddb620a..35c6331e8 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -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}`)
diff --git a/package-lock.json b/package-lock.json
index b3c962990..5ea3a58a2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -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": {
diff --git a/package.json b/package.json
index e30aef966..e978b642f 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/test/data/v17-long-history.json b/test/data/v17-long-history.json
index a33d425f8..cccb1ef96 100644
--- a/test/data/v17-long-history.json
+++ b/test/data/v17-long-history.json
@@ -54,7 +54,8 @@
"mainnet": "degraded",
"ropsten": "ok",
"kovan": "ok",
- "rinkeby": "ok"
+ "rinkeby": "ok",
+ "goerli": "ok"
}
},
"BlacklistController": {
@@ -3050,4 +3051,4 @@
]
}
}
-} \ No newline at end of file
+}
diff --git a/test/e2e/beta/contract-test/contract.js b/test/e2e/beta/contract-test/contract.js
index 65fb9377f..e1f886c58 100644
--- a/test/e2e/beta/contract-test/contract.js
+++ b/test/e2e/beta/contract-test/contract.js
@@ -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)
+ })
+ })
}
})
diff --git a/test/e2e/beta/contract-test/index.html b/test/e2e/beta/contract-test/index.html
index 0d422ef20..6e134dc36 100644
--- a/test/e2e/beta/contract-test/index.html
+++ b/test/e2e/beta/contract-test/index.html
@@ -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>
diff --git a/test/e2e/beta/from-import-beta-ui.spec.js b/test/e2e/beta/from-import-beta-ui.spec.js
index a913caa79..625330dbb 100644
--- a/test/e2e/beta/from-import-beta-ui.spec.js
+++ b/test/e2e/beta/from-import-beta-ui.spec.js
@@ -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)
diff --git a/test/e2e/beta/metamask-beta-responsive-ui.spec.js b/test/e2e/beta/metamask-beta-responsive-ui.spec.js
index b1ed8536e..9ef560556 100644
--- a/test/e2e/beta/metamask-beta-responsive-ui.spec.js
+++ b/test/e2e/beta/metamask-beta-responsive-ui.spec.js
@@ -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)
})
diff --git a/test/e2e/beta/metamask-beta-ui.spec.js b/test/e2e/beta/metamask-beta-ui.spec.js
index 05ad84f24..06778ab99 100644
--- a/test/e2e/beta/metamask-beta-ui.spec.js
+++ b/test/e2e/beta/metamask-beta-ui.spec.js
@@ -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)
})
diff --git a/test/e2e/beta/run-web3.sh b/test/e2e/beta/run-web3.sh
new file mode 100755
index 000000000..9f77060de
--- /dev/null
+++ b/test/e2e/beta/run-web3.sh
@@ -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' \ No newline at end of file
diff --git a/test/e2e/beta/web3.spec.js b/test/e2e/beta/web3.spec.js
new file mode 100644
index 000000000..b3962c821
--- /dev/null
+++ b/test/e2e/beta/web3.spec.js
@@ -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)
+
+
+ }
+ }
+ })
+
+
+ })
+
+
+ })
diff --git a/test/integration/lib/confirm-sig-requests.js b/test/integration/lib/confirm-sig-requests.js
index e4540c4f2..699527609 100644
--- a/test/integration/lib/confirm-sig-requests.js
+++ b/test/integration/lib/confirm-sig-requests.js
@@ -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)
diff --git a/test/integration/lib/currency-localization.js b/test/integration/lib/currency-localization.js
index cd10efa30..24c3a1a2d 100644
--- a/test/integration/lib/currency-localization.js
+++ b/test/integration/lib/currency-localization.js
@@ -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)
diff --git a/test/integration/lib/send-new-ui.js b/test/integration/lib/send-new-ui.js
index d7003f4cc..0cca9e959 100644
--- a/test/integration/lib/send-new-ui.js
+++ b/test/integration/lib/send-new-ui.js
@@ -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')
diff --git a/test/integration/lib/tx-list-items.js b/test/integration/lib/tx-list-items.js
index c0056dd22..e4478614e 100644
--- a/test/integration/lib/tx-list-items.js
+++ b/test/integration/lib/tx-list-items.js
@@ -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')
diff --git a/test/lib/mock-encryptor.js b/test/lib/mock-encryptor.js
index 852c536c2..23ab2404f 100644
--- a/test/lib/mock-encryptor.js
+++ b/test/lib/mock-encryptor.js
@@ -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)
},
diff --git a/test/lib/util.js b/test/lib/util.js
index 858565bb9..4c5d789d1 100644
--- a/test/lib/util.js
+++ b/test/lib/util.js
@@ -6,7 +6,7 @@ module.exports = {
}
function timeout (time) {
- return new Promise((resolve, reject) => {
+ return new Promise((resolve) => {
setTimeout(resolve, time || 1500)
})
}
diff --git a/test/unit/actions/tx_test.js b/test/unit/actions/tx_test.js
index 8c64d844f..f2f8f1d1c 100644
--- a/test/unit/actions/tx_test.js
+++ b/test/unit/actions/tx_test.js
@@ -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() },
})
diff --git a/test/unit/app/controllers/blacklist-controller-test.js b/test/unit/app/controllers/blacklist-controller-test.js
deleted file mode 100644
index 7a14c02cc..000000000
--- a/test/unit/app/controllers/blacklist-controller-test.js
+++ /dev/null
@@ -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)
- })
- })
-})
diff --git a/test/unit/app/controllers/currency-controller-test.js b/test/unit/app/controllers/currency-controller-test.js
index 7c4644d9f..8b6fbb719 100644
--- a/test/unit/app/controllers/currency-controller-test.js
+++ b/test/unit/app/controllers/currency-controller-test.js
@@ -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()
diff --git a/test/unit/app/controllers/infura-controller-test.js b/test/unit/app/controllers/infura-controller-test.js
index 7bd95dd4b..316f80378 100644
--- a/test/unit/app/controllers/infura-controller-test.js
+++ b/test/unit/app/controllers/infura-controller-test.js
@@ -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')
+ })
+ })
})
})
diff --git a/test/unit/app/controllers/metamask-controller-test.js b/test/unit/app/controllers/metamask-controller-test.js
index 1ed6a95fb..a56b8adbd 100644
--- a/test/unit/app/controllers/metamask-controller-test.js
+++ b/test/unit/app/controllers/metamask-controller-test.js
@@ -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()
diff --git a/test/unit/app/controllers/network-contoller-test.js b/test/unit/app/controllers/network-contoller-test.js
index 7959e6cc1..32f7b337d 100644
--- a/test/unit/app/controllers/network-contoller-test.js
+++ b/test/unit/app/controllers/network-contoller-test.js
@@ -92,6 +92,9 @@ describe('Network utils', () => {
}, {
input: 'mainnet',
expected: 'Main Ethereum Network',
+ }, {
+ input: 'goerli',
+ expected: 'Goerli',
},
]
diff --git a/test/unit/app/controllers/preferences-controller-test.js b/test/unit/app/controllers/preferences-controller-test.js
index 558597ae7..81b152f3d 100644
--- a/test/unit/app/controllers/preferences-controller-test.js
+++ b/test/unit/app/controllers/preferences-controller-test.js
@@ -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')
diff --git a/test/unit/app/controllers/transactions/nonce-tracker-test.js b/test/unit/app/controllers/transactions/nonce-tracker-test.js
deleted file mode 100644
index 51ac390e9..000000000
--- a/test/unit/app/controllers/transactions/nonce-tracker-test.js
+++ /dev/null
@@ -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,
- })
-}
diff --git a/test/unit/app/controllers/transactions/pending-tx-test.js b/test/unit/app/controllers/transactions/pending-tx-test.js
index 2988bf61f..b37ac2766 100644
--- a/test/unit/app/controllers/transactions/pending-tx-test.js
+++ b/test/unit/app/controllers/transactions/pending-tx-test.js
@@ -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 = [{
diff --git a/test/unit/app/controllers/transactions/recipient-blacklist-checker-test.js b/test/unit/app/controllers/transactions/recipient-blacklist-checker-test.js
index cb413545f..d3e47c67e 100644
--- a/test/unit/app/controllers/transactions/recipient-blacklist-checker-test.js
+++ b/test/unit/app/controllers/transactions/recipient-blacklist-checker-test.js
@@ -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 () {
diff --git a/test/unit/app/controllers/transactions/tx-controller-test.js b/test/unit/app/controllers/transactions/tx-controller-test.js
index 9000cd364..8ff409207 100644
--- a/test/unit/app/controllers/transactions/tx-controller-test.js
+++ b/test/unit/app/controllers/transactions/tx-controller-test.js
@@ -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([
diff --git a/test/unit/app/controllers/transactions/tx-gas-util-test.js b/test/unit/app/controllers/transactions/tx-gas-util-test.js
index 31defd6ed..f92d95507 100644
--- a/test/unit/app/controllers/transactions/tx-gas-util-test.js
+++ b/test/unit/app/controllers/transactions/tx-gas-util-test.js
@@ -11,7 +11,7 @@ describe('txUtils', function () {
before(function () {
txUtils = new TxUtils(new Proxy({}, {
- get: (obj, name) => {
+ get: () => {
return () => {}
},
}))
diff --git a/test/unit/app/controllers/transactions/tx-state-history-helper-test.js b/test/unit/app/controllers/transactions/tx-state-history-helper-test.js
index fba0e7fda..328c2ac6f 100644
--- a/test/unit/app/controllers/transactions/tx-state-history-helper-test.js
+++ b/test/unit/app/controllers/transactions/tx-state-history-helper-test.js
@@ -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) {
diff --git a/test/unit/app/controllers/transactions/tx-state-manager-test.js b/test/unit/app/controllers/transactions/tx-state-manager-test.js
index 88bdaa60e..4ccade2aa 100644
--- a/test/unit/app/controllers/transactions/tx-state-manager-test.js
+++ b/test/unit/app/controllers/transactions/tx-state-manager-test.js
@@ -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)
}
diff --git a/test/unit/app/edge-encryptor-test.js b/test/unit/app/edge-encryptor-test.js
index 1a6255b36..52817cd09 100644
--- a/test/unit/app/edge-encryptor-test.js
+++ b/test/unit/app/edge-encryptor-test.js
@@ -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()
})
diff --git a/test/unit/migrations/migrator-test.js b/test/unit/migrations/migrator-test.js
index a9374dff1..693c5830d 100644
--- a/test/unit/migrations/migrator-test.js
+++ b/test/unit/migrations/migrator-test.js
@@ -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)
})
diff --git a/test/unit/ui/app/actions.spec.js b/test/unit/ui/app/actions.spec.js
index 86c3f8aff..34dd6a39b 100644
--- a/test/unit/ui/app/actions.spec.js
+++ b/test/unit/ui/app/actions.spec.js
@@ -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'))
})
diff --git a/test/unit/ui/etherscan-prefix-for-network.spec.js b/test/unit/ui/etherscan-prefix-for-network.spec.js
index f0aeb8306..45e6e563d 100644
--- a/test/unit/ui/etherscan-prefix-for-network.spec.js
+++ b/test/unit/ui/etherscan-prefix-for-network.spec.js
@@ -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.')
+ })
+
})
diff --git a/test/web3/index.html b/test/web3/index.html
new file mode 100644
index 000000000..cbc43290c
--- /dev/null
+++ b/test/web3/index.html
@@ -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>
diff --git a/test/web3/schema.js b/test/web3/schema.js
new file mode 100644
index 000000000..61977f140
--- /dev/null
+++ b/test/web3/schema.js
@@ -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,
+ ],
+
+ },
+
+}
+
diff --git a/test/web3/web3.js b/test/web3/web3.js
new file mode 100644
index 000000000..0f7a4c3cd
--- /dev/null
+++ b/test/web3/web3.js
@@ -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)
+ }
+ })
+ })
+
+ })
+
+ })
+ })
+
diff --git a/ui/app/components/app/account-panel.js b/ui/app/components/app/account-panel.js
index 79882f34a..e61cb8ad6 100644
--- a/ui/app/components/app/account-panel.js
+++ b/ui/app/components/app/account-panel.js
@@ -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),
}
}
diff --git a/ui/app/components/app/add-token-button/index.scss b/ui/app/components/app/add-token-button/index.scss
index 39f404716..c4350a2d3 100644
--- a/ui/app/components/app/add-token-button/index.scss
+++ b/ui/app/components/app/add-token-button/index.scss
@@ -17,10 +17,7 @@
}
&__button {
- font-size: 0.75rem;
+ @extend %small-link;
margin: 1rem;
- text-transform: uppercase;
- color: $curious-blue;
- cursor: pointer;
}
}
diff --git a/ui/app/components/app/app-header/index.scss b/ui/app/components/app/app-header/index.scss
index 325844af5..d46b16f25 100644
--- a/ui/app/components/app/app-header/index.scss
+++ b/ui/app/components/app/app-header/index.scss
@@ -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%;
+ }
}
}
diff --git a/ui/app/components/app/bn-as-decimal-input.js b/ui/app/components/app/bn-as-decimal-input.js
index 9a033f893..834bab0a4 100644
--- a/ui/app/components/app/bn-as-decimal-input.js
+++ b/ui/app/components/app/bn-as-decimal-input.js
@@ -116,7 +116,7 @@ BnAsDecimalInput.prototype.render = function () {
)
}
-BnAsDecimalInput.prototype.setValid = function (message) {
+BnAsDecimalInput.prototype.setValid = function () {
this.setState({ invalid: null })
}
diff --git a/ui/app/components/app/coinbase-form.js b/ui/app/components/app/coinbase-form.js
deleted file mode 100644
index 24d287604..000000000
--- a/ui/app/components/app/coinbase-form.js
+++ /dev/null
@@ -1,69 +0,0 @@
-const Component = require('react').Component
-const PropTypes = require('prop-types')
-const h = require('react-hyperscript')
-const inherits = require('util').inherits
-const connect = require('react-redux').connect
-const actions = require('../../store/actions')
-
-CoinbaseForm.contextTypes = {
- t: PropTypes.func,
-}
-
-module.exports = connect(mapStateToProps)(CoinbaseForm)
-
-
-function mapStateToProps (state) {
- return {
- warning: state.appState.warning,
- }
-}
-
-inherits(CoinbaseForm, Component)
-
-function CoinbaseForm () {
- Component.call(this)
-}
-
-CoinbaseForm.prototype.render = function () {
- var props = this.props
-
- return h('.flex-column', {
- style: {
- marginTop: '35px',
- padding: '25px',
- width: '100%',
- },
- }, [
- h('.flex-row', {
- style: {
- justifyContent: 'space-around',
- margin: '33px',
- marginTop: '0px',
- },
- }, [
- h('button.btn-green', {
- onClick: this.toCoinbase.bind(this),
- }, this.context.t('continueToCoinbase')),
-
- h('button.btn-red', {
- onClick: () => props.dispatch(actions.goHome()),
- }, this.context.t('cancel')),
- ]),
- ])
-}
-
-CoinbaseForm.prototype.toCoinbase = function () {
- const props = this.props
- const address = props.buyView.buyAddress
- props.dispatch(actions.buyEth({ network: '1', address, amount: 0 }))
-}
-
-CoinbaseForm.prototype.renderLoading = function () {
- return h('img', {
- style: {
- width: '27px',
- marginRight: '-27px',
- },
- src: 'images/loading.svg',
- })
-}
diff --git a/ui/app/components/app/customize-gas-modal/index.js b/ui/app/components/app/customize-gas-modal/index.js
index dca77bb00..1f9436810 100644
--- a/ui/app/components/app/customize-gas-modal/index.js
+++ b/ui/app/components/app/customize-gas-modal/index.js
@@ -18,11 +18,11 @@ const {
MIN_GAS_PRICE_DEC,
MIN_GAS_LIMIT_DEC,
MIN_GAS_PRICE_GWEI,
-} = require('../send/send.constants')
+} = require('../../../pages/send/send.constants')
const {
isBalanceSufficient,
-} = require('../send/send.utils')
+} = require('../../../pages/send/send.utils')
const {
conversionUtil,
@@ -47,7 +47,7 @@ const {
const {
getGasPrice,
getGasLimit,
-} = require('../send/send.selectors')
+} = require('../../../pages/send/send.selectors')
function mapStateToProps (state) {
const selectedToken = getSelectedToken(state)
@@ -382,7 +382,7 @@ CustomizeGasModal.prototype.render = function () {
onClick: this.props.hideModal,
}, [this.context.t('cancel')]),
h(Button, {
- type: 'primary',
+ type: 'secondary',
className: 'send-v2__customize-gas__save',
onClick: () => !error && this.save(newGasPrice, gasLimit, gasTotal),
disabled: error,
diff --git a/ui/app/components/app/dropdowns/account-details-dropdown.js b/ui/app/components/app/dropdowns/account-details-dropdown.js
index 3d4598946..cbeccdd81 100644
--- a/ui/app/components/app/dropdowns/account-details-dropdown.js
+++ b/ui/app/components/app/dropdowns/account-details-dropdown.js
@@ -4,7 +4,7 @@ const h = require('react-hyperscript')
const inherits = require('util').inherits
const connect = require('react-redux').connect
const actions = require('../../../store/actions')
-const { getSelectedIdentity } = require('../../../selectors/selectors')
+const { getSelectedIdentity, getRpcPrefsForCurrentProvider } = require('../../../selectors/selectors')
const genAccountLink = require('../../../../lib/account-link.js')
const { Menu, Item, CloseArea } = require('./components/menu')
@@ -20,6 +20,7 @@ function mapStateToProps (state) {
selectedIdentity: getSelectedIdentity(state),
network: state.metamask.network,
keyrings: state.metamask.keyrings,
+ rpcPrefs: getRpcPrefsForCurrentProvider(state),
}
}
@@ -28,8 +29,8 @@ function mapDispatchToProps (dispatch) {
showAccountDetailModal: () => {
dispatch(actions.showModal({ name: 'ACCOUNT_DETAILS' }))
},
- viewOnEtherscan: (address, network) => {
- global.platform.openWindow({ url: genAccountLink(address, network) })
+ viewOnEtherscan: (address, network, rpcPrefs) => {
+ global.platform.openWindow({ url: genAccountLink(address, network, rpcPrefs) })
},
showRemoveAccountConfirmationModal: (identity) => {
return dispatch(actions.showModal({ name: 'CONFIRM_REMOVE_ACCOUNT', identity }))
@@ -56,7 +57,9 @@ AccountDetailsDropdown.prototype.render = function () {
keyrings,
showAccountDetailModal,
viewOnEtherscan,
- showRemoveAccountConfirmationModal } = this.props
+ showRemoveAccountConfirmationModal,
+ rpcPrefs,
+ } = this.props
const address = selectedIdentity.address
@@ -112,10 +115,12 @@ AccountDetailsDropdown.prototype.render = function () {
name: 'Clicked View on Etherscan',
},
})
- viewOnEtherscan(address, network)
+ viewOnEtherscan(address, network, rpcPrefs)
this.props.onClose()
},
- text: this.context.t('viewOnEtherscan'),
+ text: (rpcPrefs.blockExplorerUrl
+ ? this.context.t('blockExplorerView', [rpcPrefs.blockExplorerUrl.match(/^https?:\/\/(.+)/)[1]])
+ : this.context.t('viewOnEtherscan')),
icon: h(`img`, { src: 'images/open-etherscan.svg', style: { height: '15px' } }),
}),
isRemovable ? h(Item, {
diff --git a/ui/app/components/app/dropdowns/network-dropdown.js b/ui/app/components/app/dropdowns/network-dropdown.js
index 3d9037a06..378ad3ba6 100644
--- a/ui/app/components/app/dropdowns/network-dropdown.js
+++ b/ui/app/components/app/dropdowns/network-dropdown.js
@@ -10,7 +10,7 @@ const Dropdown = require('./components/dropdown').Dropdown
const DropdownMenuItem = require('./components/dropdown').DropdownMenuItem
const NetworkDropdownIcon = require('./components/network-dropdown-icon')
const R = require('ramda')
-const { ADVANCED_ROUTE } = require('../../../helpers/constants/routes')
+const { NETWORKS_ROUTE } = require('../../../helpers/constants/routes')
// classes from nodes of the toggle element.
const notToggleElementClassnames = [
@@ -49,6 +49,7 @@ function mapDispatchToProps (dispatch) {
},
showNetworkDropdown: () => dispatch(actions.showNetworkDropdown()),
hideNetworkDropdown: () => dispatch(actions.hideNetworkDropdown()),
+ setNetworksTabAddMode: isInAddMode => dispatch(actions.setNetworksTabAddMode(isInAddMode)),
}
}
@@ -72,7 +73,7 @@ module.exports = compose(
// TODO: specify default props and proptypes
NetworkDropdown.prototype.render = function () {
const props = this.props
- const { provider: { type: providerType, rpcTarget: activeNetwork } } = props
+ const { provider: { type: providerType, rpcTarget: activeNetwork }, setNetworksTabAddMode } = props
const rpcListDetail = props.frequentRpcListDetail
const isOpen = this.props.networkDropdownOpen
const dropdownMenuItemStyle = {
@@ -207,6 +208,28 @@ NetworkDropdown.prototype.render = function () {
h(
DropdownMenuItem,
{
+ key: 'goerli',
+ closeMenu: () => this.props.hideNetworkDropdown(),
+ onClick: () => this.handleClick('goerli'),
+ style: dropdownMenuItemStyle,
+ },
+ [
+ providerType === 'goerli' ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'),
+ h(NetworkDropdownIcon, {
+ backgroundColor: '#3099f2', // $dodger-blue
+ isSelected: providerType === 'goerli',
+ }),
+ h('span.network-name-item', {
+ style: {
+ color: providerType === 'goerli' ? '#ffffff' : '#9b9b9b',
+ },
+ }, this.context.t('goerli')),
+ ]
+ ),
+
+ h(
+ DropdownMenuItem,
+ {
key: 'default',
closeMenu: () => this.props.hideNetworkDropdown(),
onClick: () => this.handleClick('localhost'),
@@ -233,7 +256,10 @@ NetworkDropdown.prototype.render = function () {
DropdownMenuItem,
{
closeMenu: () => this.props.hideNetworkDropdown(),
- onClick: () => this.props.history.push(ADVANCED_ROUTE),
+ onClick: () => {
+ setNetworksTabAddMode(true)
+ this.props.history.push(NETWORKS_ROUTE)
+ },
style: dropdownMenuItemStyle,
},
[
@@ -285,6 +311,10 @@ NetworkDropdown.prototype.getNetworkName = function () {
name = this.context.t('kovan')
} else if (providerName === 'rinkeby') {
name = this.context.t('rinkeby')
+ } else if (providerName === 'localhost') {
+ name = this.context.t('localhost')
+ } else if (providerName === 'goerli') {
+ name = this.context.t('goerli')
} else {
name = provider.nickname || this.context.t('unknownNetwork')
}
diff --git a/ui/app/components/app/dropdowns/tests/network-dropdown.test.js b/ui/app/components/app/dropdowns/tests/network-dropdown.test.js
index 91e7899a7..4a81b973f 100644
--- a/ui/app/components/app/dropdowns/tests/network-dropdown.test.js
+++ b/ui/app/components/app/dropdowns/tests/network-dropdown.test.js
@@ -62,7 +62,7 @@ describe('Network Dropdown', () => {
})
it('renders 7 DropDownMenuItems ', () => {
- assert.equal(wrapper.find(DropdownMenuItem).length, 7)
+ assert.equal(wrapper.find(DropdownMenuItem).length, 8)
})
it('checks background color for first NetworkDropdownIcon', () => {
@@ -82,15 +82,19 @@ describe('Network Dropdown', () => {
})
it('checks background color for fifth NetworkDropdownIcon', () => {
- assert.equal(wrapper.find(NetworkDropdownIcon).at(4).prop('innerBorder'), '1px solid #9b9b9b')
+ assert.equal(wrapper.find(NetworkDropdownIcon).at(4).prop('backgroundColor'), '#3099f2') // Goerli Blue
+ })
+
+ it('checks background color for sixth NetworkDropdownIcon', () => {
+ assert.equal(wrapper.find(NetworkDropdownIcon).at(5).prop('innerBorder'), '1px solid #9b9b9b')
})
it('checks dropdown for frequestRPCList from state ', () => {
- assert.equal(wrapper.find(DropdownMenuItem).at(5).text(), '✓http://localhost:7545')
+ assert.equal(wrapper.find(DropdownMenuItem).at(6).text(), '✓http://localhost:7545')
})
- it('checks background color for sixth NetworkDropdownIcon', () => {
- assert.equal(wrapper.find(NetworkDropdownIcon).at(5).prop('innerBorder'), '1px solid #9b9b9b')
+ it('checks background color for seventh NetworkDropdownIcon', () => {
+ assert.equal(wrapper.find(NetworkDropdownIcon).at(6).prop('innerBorder'), '1px solid #9b9b9b')
})
})
diff --git a/ui/app/components/app/ens-input.js b/ui/app/components/app/ens-input.js
index 274058a1b..f17f6c3d6 100644
--- a/ui/app/components/app/ens-input.js
+++ b/ui/app/components/app/ens-input.js
@@ -10,7 +10,7 @@ const networkMap = require('ethjs-ens/lib/network-map.json')
const ensRE = /.+\..+$/
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
const connect = require('react-redux').connect
-const ToAutoComplete = require('./send/to-autocomplete').default
+const ToAutoComplete = require('../../pages/send/to-autocomplete').default
const log = require('loglevel')
const { isValidENSAddress } = require('../../helpers/utils/util')
@@ -41,12 +41,15 @@ EnsInput.prototype.onChange = function (recipient) {
ensResolution: null,
ensFailure: null,
toError: null,
+ recipient,
})
}
this.setState({
loadingEns: true,
+ recipient,
})
+
this.checkName(recipient)
}
@@ -56,6 +59,7 @@ EnsInput.prototype.render = function () {
list: 'addresses',
onChange: this.onChange.bind(this),
qrScanner: true,
+ recipient: (this.state || {}).recipient,
})
return h('div', {
style: { width: '100%', position: 'relative' },
@@ -79,19 +83,21 @@ EnsInput.prototype.componentDidMount = function () {
EnsInput.prototype.lookupEnsName = function (recipient) {
const { ensResolution } = this.state
+ recipient = recipient.trim()
log.info(`ENS attempting to resolve name: ${recipient}`)
- this.ens.lookup(recipient.trim())
+ this.ens.lookup(recipient)
.then((address) => {
if (address === ZERO_ADDRESS) throw new Error(this.context.t('noAddressForName'))
if (address !== ensResolution) {
this.setState({
loadingEns: false,
ensResolution: address,
- nickname: recipient.trim(),
+ nickname: recipient,
hoverText: address + '\n' + this.context.t('clickCopy'),
ensFailure: false,
toError: null,
+ recipient,
})
}
})
@@ -101,11 +107,11 @@ EnsInput.prototype.lookupEnsName = function (recipient) {
ensResolution: recipient,
ensFailure: true,
toError: null,
+ recipient: null,
}
if (isValidENSAddress(recipient) && reason.message === 'ENS name not defined.') {
setStateObj.hoverText = this.context.t('ensNameNotFound')
setStateObj.toError = 'ensNameNotFound'
- setStateObj.ensFailure = false
} else {
log.error(reason)
setStateObj.hoverText = reason.message
@@ -128,7 +134,7 @@ EnsInput.prototype.componentDidUpdate = function (prevProps, prevState) {
}
if (prevState && ensResolution && this.props.onChange &&
ensResolution !== prevState.ensResolution) {
- this.props.onChange({ toAddress: ensResolution, nickname, toError: state.toError, toWarning: state.toWarning })
+ this.props.onChange({ toAddress: ensResolution, recipient: state.recipient, nickname, toError: state.toError, toWarning: state.toWarning })
}
}
@@ -144,7 +150,7 @@ EnsInput.prototype.ensIcon = function (recipient) {
}, this.ensIconContents(recipient))
}
-EnsInput.prototype.ensIconContents = function (recipient) {
+EnsInput.prototype.ensIconContents = function () {
const { loadingEns, ensFailure, ensResolution, toError } = this.state || { ensResolution: ZERO_ADDRESS }
if (toError) return
diff --git a/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js b/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js
index 95894140c..d6c259033 100644
--- a/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js
+++ b/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.component.js
@@ -58,7 +58,7 @@ export default class AdvancedTabContent extends Component {
}
}
- gasInput ({ labelKey, value, onChange, insufficientBalance, showGWEI, customPriceIsSafe, isSpeedUp }) {
+ gasInput ({ labelKey, value, onChange, insufficientBalance, customPriceIsSafe, isSpeedUp }) {
const {
isInError,
errorText,
diff --git a/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.container.js b/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.container.js
index 90fef1a1b..73bc13481 100644
--- a/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.container.js
+++ b/ui/app/components/app/gas-customization/advanced-gas-inputs/advanced-gas-inputs.container.js
@@ -17,8 +17,8 @@ function convertGasLimitForInputs (gasLimitInHexWEI) {
const mapDispatchToProps = dispatch => {
return {
- showGasPriceInfoModal: modalName => dispatch(showModal({ name: 'GAS_PRICE_INFO_MODAL' })),
- showGasLimitInfoModal: modalName => dispatch(showModal({ name: 'GAS_LIMIT_INFO_MODAL' })),
+ showGasPriceInfoModal: () => dispatch(showModal({ name: 'GAS_PRICE_INFO_MODAL' })),
+ showGasLimitInfoModal: () => dispatch(showModal({ name: 'GAS_LIMIT_INFO_MODAL' })),
}
}
diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js
index ad8628621..eab3434df 100644
--- a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js
+++ b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/advanced-tab-content.component.js
@@ -67,7 +67,7 @@ export default class AdvancedTabContent extends Component {
}
}
- gasInput ({ labelKey, value, onChange, insufficientBalance, showGWEI, customPriceIsSafe, isSpeedUp }) {
+ gasInput ({ labelKey, value, onChange, insufficientBalance, customPriceIsSafe, isSpeedUp }) {
const {
isInError,
errorText,
@@ -148,7 +148,6 @@ export default class AdvancedTabContent extends Component {
customGasPrice,
updateCustomGasPrice,
customGasLimit,
- updateCustomGasLimit,
insufficientBalance,
customPriceIsSafe,
isSpeedUp,
diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js b/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js
index 8aaccafd5..e18c1067e 100644
--- a/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js
+++ b/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.component.js
@@ -122,8 +122,6 @@ export default class GasModalPageContainer extends Component {
}
renderTabs ({
- originalTotalFiat,
- originalTotalEth,
newTotalFiat,
newTotalEth,
sendAmount,
diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js b/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js
index d541056f4..9da9a2ef6 100644
--- a/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js
+++ b/ui/app/components/app/gas-customization/gas-modal-page-container/gas-modal-page-container.container.js
@@ -7,6 +7,8 @@ import {
setGasPrice,
createSpeedUpTransaction,
hideSidebar,
+ updateSendAmount,
+ setGasTotal,
} from '../../../../store/actions'
import {
setCustomGasPrice,
@@ -18,6 +20,7 @@ import {
} from '../../../../ducks/gas/gas.duck'
import {
hideGasButtonGroup,
+ updateSendErrors,
} from '../../../../ducks/send/send.duck'
import {
updateGasAndCalculate,
@@ -46,6 +49,9 @@ import {
isCustomPriceSafe,
} from '../../../../selectors/custom-gas'
import {
+ getTokenBalance,
+} from '../../../../pages/send/send.selectors'
+import {
submittedPendingTransactionsSelector,
} from '../../../../selectors/transactions'
import {
@@ -53,6 +59,7 @@ import {
} from '../../../../helpers/utils/confirm-tx.util'
import {
addHexWEIsToDec,
+ subtractHexWEIsToDec,
decEthToConvertedCurrency as ethTotalToConvertedCurrency,
decGWEIToHexWEI,
hexWEIToDecGWEI,
@@ -63,9 +70,11 @@ import {
import {
calcGasTotal,
isBalanceSufficient,
-} from '../../send/send.utils'
+} from '../../../../pages/send/send.utils'
import { addHexPrefix } from 'ethereumjs-util'
import { getAdjacentGasPrices, extrapolateY } from '../gas-price-chart/gas-price-chart.utils'
+import { getMaxModeOn } from '../../../../pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.selectors'
+import { calcMaxAmount } from '../../../../pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.utils'
const mapStateToProps = (state, ownProps) => {
const { transaction = {} } = ownProps
@@ -75,8 +84,6 @@ const mapStateToProps = (state, ownProps) => {
const { gasPrice: currentGasPrice, gas: currentGasLimit, value } = getTxParams(state, transaction.id)
const customModalGasPriceInHex = getCustomGasPrice(state) || currentGasPrice
const customModalGasLimitInHex = getCustomGasLimit(state) || currentGasLimit
- const gasTotal = calcGasTotal(customModalGasLimitInHex, customModalGasPriceInHex)
-
const customGasTotal = calcGasTotal(customModalGasLimitInHex, customModalGasPriceInHex)
const gasButtonInfo = getRenderableBasicEstimateData(state, customModalGasLimitInHex)
@@ -90,6 +97,8 @@ const mapStateToProps = (state, ownProps) => {
const customGasPrice = calcCustomGasPrice(customModalGasPriceInHex)
+ const maxModeOn = getMaxModeOn(state)
+
const gasPrices = getEstimatedGasPrices(state)
const estimatedTimes = getEstimatedGasTimes(state)
const balance = getCurrentEthBalance(state)
@@ -98,9 +107,13 @@ const mapStateToProps = (state, ownProps) => {
const isMainnet = getIsMainnet(state)
const showFiat = Boolean(isMainnet || showFiatInTestnets)
- const insufficientBalance = !isBalanceSufficient({
+ const newTotalEth = maxModeOn ? addHexWEIsToRenderableEth(balance, '0x0') : addHexWEIsToRenderableEth(value, customGasTotal)
+
+ const sendAmount = maxModeOn ? subtractHexWEIsFromRenderableEth(balance, customGasTotal) : addHexWEIsToRenderableEth(value, '0x0')
+
+ const insufficientBalance = maxModeOn ? false : !isBalanceSufficient({
amount: value,
- gasTotal,
+ gasTotal: customGasTotal,
balance,
conversionRate,
})
@@ -112,10 +125,12 @@ const mapStateToProps = (state, ownProps) => {
customModalGasLimitInHex,
customGasPrice,
customGasLimit: calcCustomGasLimit(customModalGasLimitInHex),
+ customGasTotal,
newTotalFiat,
currentTimeEstimate: getRenderableTimeEstimate(customGasPrice, gasPrices, estimatedTimes),
blockTime: getBasicGasEstimateBlockTime(state),
customPriceIsSafe: isCustomPriceSafe(state),
+ maxModeOn,
gasPriceButtonGroupProps: {
buttonDataLoading,
defaultActiveButtonIndex: getDefaultActiveButtonIndex(gasButtonInfo, customModalGasPriceInHex),
@@ -129,12 +144,12 @@ const mapStateToProps = (state, ownProps) => {
estimatedTimesMax: estimatedTimes[0],
},
infoRowProps: {
- originalTotalFiat: addHexWEIsToRenderableFiat(value, gasTotal, currentCurrency, conversionRate),
- originalTotalEth: addHexWEIsToRenderableEth(value, gasTotal),
+ originalTotalFiat: addHexWEIsToRenderableFiat(value, customGasTotal, currentCurrency, conversionRate),
+ originalTotalEth: addHexWEIsToRenderableEth(value, customGasTotal),
newTotalFiat: showFiat ? newTotalFiat : '',
- newTotalEth: addHexWEIsToRenderableEth(value, customGasTotal),
+ newTotalEth,
transactionFee: addHexWEIsToRenderableEth('0x0', customGasTotal),
- sendAmount: addHexWEIsToRenderableEth(value, '0x0'),
+ sendAmount,
},
isSpeedUp: transaction.status === 'submitted',
txId: transaction.id,
@@ -142,6 +157,9 @@ const mapStateToProps = (state, ownProps) => {
gasEstimatesLoading,
isMainnet,
isEthereumNetwork: isEthereumNetwork(state),
+ selectedToken: getSelectedToken(state),
+ balance,
+ tokenBalance: getTokenBalance(state),
}
}
@@ -174,11 +192,29 @@ const mapDispatchToProps = dispatch => {
hideSidebar: () => dispatch(hideSidebar()),
fetchGasEstimates: (blockTime) => dispatch(fetchGasEstimates(blockTime)),
fetchBasicGasAndTimeEstimates: () => dispatch(fetchBasicGasAndTimeEstimates()),
+ setGasTotal: (total) => dispatch(setGasTotal(total)),
+ setAmountToMax: (maxAmountDataObject) => {
+ dispatch(updateSendErrors({ amount: null }))
+ dispatch(updateSendAmount(calcMaxAmount(maxAmountDataObject)))
+ },
}
}
const mergeProps = (stateProps, dispatchProps, ownProps) => {
- const { gasPriceButtonGroupProps, isConfirm, txId, isSpeedUp, insufficientBalance, customGasPrice } = stateProps
+ const {
+ gasPriceButtonGroupProps,
+ isConfirm,
+ txId,
+ isSpeedUp,
+ insufficientBalance,
+ maxModeOn,
+ customGasPrice,
+ customGasTotal,
+ balance,
+ selectedToken,
+ tokenBalance,
+ customGasLimit,
+ } = stateProps
const {
updateCustomGasPrice: dispatchUpdateCustomGasPrice,
hideGasButtonGroup: dispatchHideGasButtonGroup,
@@ -188,6 +224,7 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
hideSidebar: dispatchHideSidebar,
cancelAndClose: dispatchCancelAndClose,
hideModal: dispatchHideModal,
+ setAmountToMax: dispatchSetAmountToMax,
...otherDispatchProps
} = dispatchProps
@@ -208,6 +245,14 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
dispatchHideGasButtonGroup()
dispatchCancelAndClose()
}
+ if (maxModeOn) {
+ dispatchSetAmountToMax({
+ balance,
+ gasTotal: customGasTotal,
+ selectedToken,
+ tokenBalance,
+ })
+ }
},
gasPriceButtonGroupProps: {
...gasPriceButtonGroupProps,
@@ -219,7 +264,7 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
dispatchHideSidebar()
}
},
- disableSave: insufficientBalance || (isSpeedUp && customGasPrice === 0),
+ disableSave: insufficientBalance || (isSpeedUp && customGasPrice === 0) || customGasLimit < 21000,
}
}
@@ -258,6 +303,13 @@ function addHexWEIsToRenderableEth (aHexWEI, bHexWEI) {
)(aHexWEI, bHexWEI)
}
+function subtractHexWEIsFromRenderableEth (aHexWEI, bHexWei) {
+ return pipe(
+ subtractHexWEIsToDec,
+ formatETHFee
+ )(aHexWEI, bHexWei)
+}
+
function addHexWEIsToRenderableFiat (aHexWEI, bHexWEI, convertedCurrency, conversionRate) {
return pipe(
addHexWEIsToDec,
diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js b/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js
index ab24b9c0e..dbe61d5cf 100644
--- a/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js
+++ b/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-container.test.js
@@ -46,6 +46,10 @@ proxyquire('../gas-modal-page-container.container.js', {
'../../../../ducks/send/send.duck': sendActionSpies,
'../../../../selectors/selectors.js': {
getCurrentEthBalance: (state) => state.metamask.balance || '0x0',
+ getSelectedToken: () => null,
+ },
+ '../../../../pages/send/send.selectors': {
+ getTokenBalance: (state) => state.metamask.send.tokenBalance || '0x0',
},
})
@@ -68,6 +72,7 @@ describe('gas-modal-page-container container', () => {
gasLimit: '16',
gasPrice: '32',
amount: '64',
+ maxModeOn: false,
},
currentCurrency: 'abc',
conversionRate: 50,
@@ -106,6 +111,7 @@ describe('gas-modal-page-container container', () => {
},
}
const baseExpectedResult = {
+ balance: '0x0',
isConfirm: true,
customGasPrice: 4.294967295,
customGasLimit: 2863311530,
@@ -114,6 +120,7 @@ describe('gas-modal-page-container container', () => {
blockTime: 12,
customModalGasLimitInHex: 'aaaaaaaa',
customModalGasPriceInHex: 'ffffffff',
+ customGasTotal: 'aaaaaaa955555556',
customPriceIsSafe: true,
gasChartProps: {
'currentPrice': 4.294967295,
@@ -142,6 +149,9 @@ describe('gas-modal-page-container container', () => {
txId: 34,
isEthereumNetwork: true,
isMainnet: true,
+ maxModeOn: false,
+ selectedToken: null,
+ tokenBalance: '0x0',
}
const baseMockOwnProps = { transaction: { id: 34 } }
const tests = [
@@ -150,7 +160,7 @@ describe('gas-modal-page-container container', () => {
mockState: Object.assign({}, baseMockState, {
metamask: { ...baseMockState.metamask, balance: '0xfffffffffffffffffffff' },
}),
- expectedResult: Object.assign({}, baseExpectedResult, { insufficientBalance: false }),
+ expectedResult: Object.assign({}, baseExpectedResult, { balance: '0xfffffffffffffffffffff', insufficientBalance: false }),
mockOwnProps: baseMockOwnProps,
},
{
diff --git a/ui/app/components/app/gas-customization/gas-price-button-group/gas-price-button-group.component.js b/ui/app/components/app/gas-customization/gas-price-button-group/gas-price-button-group.component.js
index 0456f5262..14952a49a 100644
--- a/ui/app/components/app/gas-customization/gas-price-button-group/gas-price-button-group.component.js
+++ b/ui/app/components/app/gas-customization/gas-price-button-group/gas-price-button-group.component.js
@@ -49,7 +49,7 @@ export default class GasPriceButtonGroup extends Component {
priceInHexWei,
...renderableGasInfo
}, {
- buttonDataLoading,
+ buttonDataLoading: _,
handleGasPriceSelection,
...buttonContentPropsAndFlags
}, index) {
diff --git a/ui/app/components/app/gas-customization/gas-price-button-group/index.scss b/ui/app/components/app/gas-customization/gas-price-button-group/index.scss
index cb2f3ecf1..92b4aba42 100644
--- a/ui/app/components/app/gas-customization/gas-price-button-group/index.scss
+++ b/ui/app/components/app/gas-customization/gas-price-button-group/index.scss
@@ -65,6 +65,7 @@
.gas-price-button-group--small {
display: flex;
justify-content: stretch;
+ height: 54px;
@media screen and (max-width: $break-small) {
max-width: 260px;
@@ -80,10 +81,14 @@
&__label {
font-weight: 500;
+ line-height: 16px;
+ padding-bottom: 4px;
}
&__primary-currency {
font-size: 12px;
+ line-height: 12px;
+ padding-bottom: 2px;
@media screen and (max-width: 575px) {
font-size: 10px;
@@ -92,6 +97,8 @@
&__secondary-currency {
font-size: 12px;
+ line-height: 12px;
+ padding-bottom: 2px;
@media screen and (max-width: 575px) {
font-size: 10px;
@@ -99,19 +106,13 @@
}
&__loading-container {
- height: 78px;
+ height: 54px;
}
.button-group__button, .button-group__button--active {
- height: 78px;
background: white;
color: $scorpion;
- padding-top: 9px;
- padding-left: 8.5px;
-
- @media screen and (max-width: $break-small) {
- padding-left: 4px;
- }
+ padding: 0 4px;
div {
display: flex;
diff --git a/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.utils.js b/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.utils.js
index f19dafcc1..55512ce09 100644
--- a/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.utils.js
+++ b/ui/app/components/app/gas-customization/gas-price-chart/gas-price-chart.utils.js
@@ -68,7 +68,7 @@ export function handleChartUpdate ({ chart, gasPrices, newPrice, cssId }) {
export function getAdjacentGasPrices ({ gasPrices, priceToPosition }) {
const closestLowerValueIndex = gasPrices.findIndex((e, i, a) => e <= priceToPosition && a[i + 1] >= priceToPosition)
- const closestHigherValueIndex = gasPrices.findIndex((e, i, a) => e > priceToPosition)
+ const closestHigherValueIndex = gasPrices.findIndex((e) => e > priceToPosition)
return {
closestLowerValueIndex,
closestHigherValueIndex,
@@ -133,7 +133,7 @@ export function setTickPosition (axis, n, newPosition, secondNewPosition) {
d3.select('#chart')
.select(`.c3-axis-${axis}`)
.selectAll('.tick')
- .filter((d, i) => i === n)
+ .filter((_, i) => i === n)
.select('text')
.attr(positionToShift, 0)
.select('tspan')
@@ -284,7 +284,7 @@ export function generateChart (gasPrices, estimatedTimes, gasPricesMax, estimate
})
return text + '</table>' + "<div class='tooltip-arrow'></div>"
},
- position: function (data) {
+ position: function () {
if (d3.select('#overlayed-circle').empty()) {
return { top: -100, left: -100 }
}
diff --git a/ui/app/components/app/gas-customization/gas-price-chart/tests/gas-price-chart.component.test.js b/ui/app/components/app/gas-customization/gas-price-chart/tests/gas-price-chart.component.test.js
index 7dec7a85f..c960f49a7 100644
--- a/ui/app/components/app/gas-customization/gas-price-chart/tests/gas-price-chart.component.test.js
+++ b/ui/app/components/app/gas-customization/gas-price-chart/tests/gas-price-chart.component.test.js
@@ -6,7 +6,7 @@ import shallow from '../../../../../../lib/shallow-with-context'
import * as d3 from 'd3'
function timeout (time) {
- return new Promise((resolve, reject) => {
+ return new Promise((resolve) => {
setTimeout(resolve, time)
})
}
diff --git a/ui/app/components/app/loading-network-screen/loading-network-screen.component.js b/ui/app/components/app/loading-network-screen/loading-network-screen.component.js
index 348a997c8..97b16d08f 100644
--- a/ui/app/components/app/loading-network-screen/loading-network-screen.component.js
+++ b/ui/app/components/app/loading-network-screen/loading-network-screen.component.js
@@ -45,6 +45,10 @@ export default class LoadingNetworkScreen extends PureComponent {
name = this.context.t('connectingToKovan')
} else if (providerName === 'rinkeby') {
name = this.context.t('connectingToRinkeby')
+ } else if (providerName === 'localhost') {
+ name = this.context.t('connectingToLocalhost')
+ } else if (providerName === 'goerli') {
+ name = this.context.t('connectingToGoerli')
} else {
name = this.context.t('connectingTo', [providerId])
}
diff --git a/ui/app/components/app/modal/modal.component.js b/ui/app/components/app/modal/modal.component.js
index 49e131b3c..44b180ac8 100644
--- a/ui/app/components/app/modal/modal.component.js
+++ b/ui/app/components/app/modal/modal.component.js
@@ -20,7 +20,7 @@ export default class Modal extends PureComponent {
}
static defaultProps = {
- submitType: 'primary',
+ submitType: 'secondary',
cancelType: 'default',
}
diff --git a/ui/app/components/app/modal/tests/modal.component.test.js b/ui/app/components/app/modal/tests/modal.component.test.js
index a13d7c06a..5922177a6 100644
--- a/ui/app/components/app/modal/tests/modal.component.test.js
+++ b/ui/app/components/app/modal/tests/modal.component.test.js
@@ -12,7 +12,7 @@ describe('Modal Component', () => {
assert.equal(wrapper.find('.modal-container').length, 1)
const buttons = wrapper.find(Button)
assert.equal(buttons.length, 1)
- assert.equal(buttons.at(0).props().type, 'primary')
+ assert.equal(buttons.at(0).props().type, 'secondary')
})
it('should render a modal with a cancel and a submit button', () => {
@@ -38,7 +38,7 @@ describe('Modal Component', () => {
cancelButton.simulate('click')
assert.equal(handleCancel.callCount, 1)
- assert.equal(submitButton.props().type, 'primary')
+ assert.equal(submitButton.props().type, 'secondary')
assert.equal(submitButton.props().children, 'Submit')
assert.equal(handleSubmit.callCount, 0)
submitButton.simulate('click')
diff --git a/ui/app/components/app/modals/account-details-modal.js b/ui/app/components/app/modals/account-details-modal.js
index 94ed04df9..6cffc918b 100644
--- a/ui/app/components/app/modals/account-details-modal.js
+++ b/ui/app/components/app/modals/account-details-modal.js
@@ -5,7 +5,7 @@ const inherits = require('util').inherits
const connect = require('react-redux').connect
const actions = require('../../../store/actions')
const AccountModalContainer = require('./account-modal-container')
-const { getSelectedIdentity } = require('../../../selectors/selectors')
+const { getSelectedIdentity, getRpcPrefsForCurrentProvider } = require('../../../selectors/selectors')
const genAccountLink = require('../../../../lib/account-link.js')
const QrView = require('../../ui/qr-code')
const EditableLabel = require('../../ui/editable-label')
@@ -17,6 +17,7 @@ function mapStateToProps (state) {
network: state.metamask.network,
selectedIdentity: getSelectedIdentity(state),
keyrings: state.metamask.keyrings,
+ rpcPrefs: getRpcPrefsForCurrentProvider(state),
}
}
@@ -54,6 +55,7 @@ AccountDetailsModal.prototype.render = function () {
showExportPrivateKeyModal,
setAccountLabel,
keyrings,
+ rpcPrefs,
} = this.props
const { name, address } = selectedIdentity
@@ -84,15 +86,19 @@ AccountDetailsModal.prototype.render = function () {
h('div.account-modal-divider'),
h(Button, {
- type: 'primary',
+ type: 'secondary',
className: 'account-modal__button',
- onClick: () => global.platform.openWindow({ url: genAccountLink(address, network) }),
- }, this.context.t('etherscanView')),
+ onClick: () => {
+ global.platform.openWindow({ url: genAccountLink(address, network, rpcPrefs) })
+ },
+ }, (rpcPrefs.blockExplorerUrl
+ ? this.context.t('blockExplorerView', [rpcPrefs.blockExplorerUrl.match(/^https?:\/\/(.+)/)[1]])
+ : this.context.t('viewOnEtherscan'))),
// Holding on redesign for Export Private Key functionality
exportPrivateKeyFeatureEnabled ? h(Button, {
- type: 'primary',
+ type: 'secondary',
className: 'account-modal__button',
onClick: () => showExportPrivateKeyModal(),
}, this.context.t('exportPrivateKey')) : null,
diff --git a/ui/app/components/app/modals/customize-gas/customize-gas.component.js b/ui/app/components/app/modals/customize-gas/customize-gas.component.js
index 5db5c79e7..387da2f79 100644
--- a/ui/app/components/app/modals/customize-gas/customize-gas.component.js
+++ b/ui/app/components/app/modals/customize-gas/customize-gas.component.js
@@ -2,7 +2,7 @@ import React, { Component } from 'react'
import PropTypes from 'prop-types'
import BigNumber from 'bignumber.js'
import GasModalCard from '../../customize-gas-modal/gas-modal-card'
-import { MIN_GAS_PRICE_GWEI } from '../../send/send.constants'
+import { MIN_GAS_PRICE_GWEI } from '../../../../pages/send/send.constants'
import Button from '../../../ui/button'
import {
@@ -128,7 +128,7 @@ export default class CustomizeGas extends Component {
{ t('cancel') }
</Button>
<Button
- type="primary"
+ type="secondary"
className="customize-gas__save"
onClick={() => {
metricsEvent({
diff --git a/ui/app/components/app/modals/deposit-ether-modal.js b/ui/app/components/app/modals/deposit-ether-modal.js
index 6f622a17c..f56069d65 100644
--- a/ui/app/components/app/modals/deposit-ether-modal.js
+++ b/ui/app/components/app/modals/deposit-ether-modal.js
@@ -48,7 +48,7 @@ function mapDispatchToProps (dispatch) {
}
inherits(DepositEtherModal, Component)
-function DepositEtherModal (props, context) {
+function DepositEtherModal (_, context) {
Component.call(this)
// need to set after i18n locale has loaded
@@ -119,7 +119,7 @@ DepositEtherModal.prototype.renderRow = function ({
!hideButton && h('div.deposit-ether-modal__buy-row__button', [
h(Button, {
- type: 'primary',
+ type: 'secondary',
className: 'deposit-ether-modal__deposit-button',
large: true,
onClick: onButtonClick,
@@ -133,7 +133,7 @@ DepositEtherModal.prototype.render = function () {
const { network, toWyre, toCoinSwitch, address, toFaucet } = this.props
const { buyingWithShapeshift } = this.state
- const isTestNetwork = ['3', '4', '42'].find(n => n === network)
+ const isTestNetwork = ['3', '4', '5', '42'].find(n => n === network)
const networkName = getNetworkDisplayName(network)
return h('div.page-container.page-container--full-width.page-container--full-height', {}, [
diff --git a/ui/app/components/app/modals/edit-account-name-modal.js b/ui/app/components/app/modals/edit-account-name-modal.js
index 41a9862e9..aa21765c4 100644
--- a/ui/app/components/app/modals/edit-account-name-modal.js
+++ b/ui/app/components/app/modals/edit-account-name-modal.js
@@ -66,7 +66,7 @@ EditAccountNameModal.prototype.render = function () {
value: this.state.inputText,
}, []),
- h('button.btn-clear.edit-account-name-modal-save-button.allcaps', {
+ h('button.button.btn-secondary.edit-account-name-modal-save-button.allcaps', {
onClick: () => {
if (this.state.inputText.length !== 0) {
setAccountLabel(identity.address, this.state.inputText)
diff --git a/ui/app/components/app/modals/export-private-key-modal.js b/ui/app/components/app/modals/export-private-key-modal.js
index 639887d4c..c3098a16c 100644
--- a/ui/app/components/app/modals/export-private-key-modal.js
+++ b/ui/app/components/app/modals/export-private-key-modal.js
@@ -98,7 +98,7 @@ ExportPrivateKeyModal.prototype.renderPasswordInput = function (privateKey) {
})
}
-ExportPrivateKeyModal.prototype.renderButtons = function (privateKey, password, address, hideModal) {
+ExportPrivateKeyModal.prototype.renderButtons = function (privateKey, address, hideModal) {
return h('div.export-private-key-buttons', {}, [
!privateKey && h(Button, {
type: 'default',
@@ -110,14 +110,14 @@ ExportPrivateKeyModal.prototype.renderButtons = function (privateKey, password,
(privateKey
? (
h(Button, {
- type: 'primary',
+ type: 'secondary',
large: true,
className: 'export-private-key__button',
onClick: () => hideModal(),
}, this.context.t('done'))
) : (
h(Button, {
- type: 'primary',
+ type: 'secondary',
large: true,
className: 'export-private-key__button',
onClick: () => this.exportAccountAndGetPrivateKey(this.state.password, address),
@@ -171,7 +171,7 @@ ExportPrivateKeyModal.prototype.render = function () {
h('div.private-key-password-warning', this.context.t('privateKeyWarning')),
- this.renderButtons(privateKey, this.state.password, address, hideModal),
+ this.renderButtons(privateKey, address, hideModal),
])
}
diff --git a/ui/app/components/app/modals/hide-token-confirmation-modal.js b/ui/app/components/app/modals/hide-token-confirmation-modal.js
index 8a9a48fd2..e2b098923 100644
--- a/ui/app/components/app/modals/hide-token-confirmation-modal.js
+++ b/ui/app/components/app/modals/hide-token-confirmation-modal.js
@@ -67,12 +67,12 @@ HideTokenConfirmationModal.prototype.render = function () {
]),
h('div.hide-token-confirmation__buttons', {}, [
- h('button.btn-cancel.hide-token-confirmation__button.allcaps', {
+ h('button.btn-default.hide-token-confirmation__button.btn--large', {
onClick: () => hideModal(),
}, [
this.context.t('cancel'),
]),
- h('button.btn-clear.hide-token-confirmation__button.allcaps', {
+ h('button.btn-secondary.hide-token-confirmation__button.btn--large', {
onClick: () => hideToken(address),
}, [
this.context.t('hide'),
diff --git a/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.container.js b/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.container.js
index 83595281f..ea7d71a73 100644
--- a/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.container.js
+++ b/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.container.js
@@ -4,7 +4,7 @@ import MetaMetricsOptInModal from './metametrics-opt-in-modal.component'
import withModalProps from '../../../../helpers/higher-order-components/with-modal-props'
import { setParticipateInMetaMetrics } from '../../../../store/actions'
-const mapStateToProps = (state, ownProps) => {
+const mapStateToProps = (_, ownProps) => {
const { unapprovedTxCount } = ownProps
return {
diff --git a/ui/app/components/app/modals/notification-modal.js b/ui/app/components/app/modals/notification-modal.js
index 2d73b2cfa..b8503ec1a 100644
--- a/ui/app/components/app/modals/notification-modal.js
+++ b/ui/app/components/app/modals/notification-modal.js
@@ -37,11 +37,11 @@ class NotificationModal extends Component {
showButtons && h('div.notification-modal__buttons', [
- showCancelButton && h('div.btn-cancel.notification-modal__buttons__btn', {
+ showCancelButton && h('div.btn-default.notification-modal__buttons__btn', {
onClick: hideModal,
}, 'Cancel'),
- showConfirmButton && h('div.btn-clear.notification-modal__buttons__btn', {
+ showConfirmButton && h('div.button.btn-secondary.notification-modal__buttons__btn', {
onClick: () => {
onConfirm()
hideModal()
diff --git a/ui/app/components/app/modals/qr-scanner/qr-scanner.component.js b/ui/app/components/app/modals/qr-scanner/qr-scanner.component.js
index 20915b5f9..a83ba8f8e 100644
--- a/ui/app/components/app/modals/qr-scanner/qr-scanner.component.js
+++ b/ui/app/components/app/modals/qr-scanner/qr-scanner.component.js
@@ -71,7 +71,7 @@ export default class QrScanner extends Component {
initCamera () {
this.codeReader = new BrowserQRCodeReader()
this.codeReader.getVideoInputDevices()
- .then(videoInputDevices => {
+ .then(() => {
clearTimeout(this.permissionChecker)
this.checkPermisisions()
this.codeReader.decodeFromInputVideoDevice(undefined, 'video')
diff --git a/ui/app/components/app/modals/reject-transactions/reject-transactions.container.js b/ui/app/components/app/modals/reject-transactions/reject-transactions.container.js
index d2af05573..aa74fd800 100644
--- a/ui/app/components/app/modals/reject-transactions/reject-transactions.container.js
+++ b/ui/app/components/app/modals/reject-transactions/reject-transactions.container.js
@@ -3,7 +3,7 @@ import { compose } from 'recompose'
import RejectTransactionsModal from './reject-transactions.component'
import withModalProps from '../../../../helpers/higher-order-components/with-modal-props'
-const mapStateToProps = (state, ownProps) => {
+const mapStateToProps = (_, ownProps) => {
const { unapprovedTxCount } = ownProps
return {
diff --git a/ui/app/components/app/network-display/index.scss b/ui/app/components/app/network-display/index.scss
index e9f2f2057..d70786d20 100644
--- a/ui/app/components/app/network-display/index.scss
+++ b/ui/app/components/app/network-display/index.scss
@@ -26,6 +26,10 @@
&--rinkeby {
background-color: lighten($tulip-tree, 35%);
}
+
+ &--goerli {
+ background-color: lighten($dodger-blue, 35%);
+ }
}
&__name {
@@ -53,5 +57,9 @@
&--rinkeby {
background-color: $tulip-tree;
}
+
+ &--goerli {
+ background-color: $dodger-blue;
+ }
}
}
diff --git a/ui/app/components/app/network-display/network-display.component.js b/ui/app/components/app/network-display/network-display.component.js
index 1142e8606..9ef5341b0 100644
--- a/ui/app/components/app/network-display/network-display.component.js
+++ b/ui/app/components/app/network-display/network-display.component.js
@@ -6,12 +6,14 @@ import {
ROPSTEN_CODE,
RINKEYBY_CODE,
KOVAN_CODE,
+ GOERLI_CODE,
} from '../../../../../app/scripts/controllers/network/enums'
const networkToClassHash = {
[MAINNET_CODE]: 'mainnet',
[ROPSTEN_CODE]: 'ropsten',
[RINKEYBY_CODE]: 'rinkeby',
+ [GOERLI_CODE]: 'goerli',
[KOVAN_CODE]: 'kovan',
}
diff --git a/ui/app/components/app/network.js b/ui/app/components/app/network.js
index e18404f42..9ee0a1e17 100644
--- a/ui/app/components/app/network.js
+++ b/ui/app/components/app/network.js
@@ -41,15 +41,15 @@ Network.prototype.render = function () {
} else if (providerName === 'ropsten') {
hoverText = context.t('ropsten')
iconName = 'ropsten-test-network'
- } else if (parseInt(networkNumber) === 3) {
- hoverText = context.t('ropsten')
- iconName = 'ropsten-test-network'
} else if (providerName === 'kovan') {
hoverText = context.t('kovan')
iconName = 'kovan-test-network'
} else if (providerName === 'rinkeby') {
hoverText = context.t('rinkeby')
iconName = 'rinkeby-test-network'
+ } else if (providerName === 'goerli') {
+ hoverText = context.t('goerli')
+ iconName = 'goerli-test-network'
} else {
hoverText = providerId
iconName = 'private-network'
@@ -60,9 +60,10 @@ Network.prototype.render = function () {
className: classnames({
'network-component--disabled': this.props.disabled,
'ethereum-network': providerName === 'mainnet',
- 'ropsten-test-network': providerName === 'ropsten' || parseInt(networkNumber) === 3,
+ 'ropsten-test-network': providerName === 'ropsten',
'kovan-test-network': providerName === 'kovan',
'rinkeby-test-network': providerName === 'rinkeby',
+ 'goerli-test-network': providerName === 'goerli',
}),
title: hoverText,
onClick: (event) => {
@@ -113,33 +114,34 @@ Network.prototype.render = function () {
h('.network-name', context.t('rinkeby')),
h('i.fa.fa-chevron-down.fa-lg.network-caret'),
])
+ case 'goerli-test-network':
+ return h('.network-indicator', [
+ h(NetworkDropdownIcon, {
+ backgroundColor: '#3099f2', // $dodger-blue
+ nonSelectBackgroundColor: '#ecb23e',
+ loading: networkNumber === 'loading',
+ }),
+ h('.network-name', context.t('goerli')),
+ h('i.fa.fa-chevron-down.fa-lg.network-caret'),
+ ])
default:
return h('.network-indicator', [
networkNumber === 'loading'
- ? h('span.pointer.network-indicator', {
- style: {
- display: 'flex',
- alignItems: 'center',
- flexDirection: 'row',
- },
+ ? h('span.pointer.network-loading-spinner', {
onClick: (event) => this.props.onClick(event),
}, [
h('img', {
title: context.t('attemptingConnect'),
- style: {
- width: '27px',
- },
src: 'images/loading.svg',
}),
])
: h('i.fa.fa-question-circle.fa-lg', {
style: {
- margin: '10px',
color: 'rgb(125, 128, 130)',
},
}),
- h('.network-name', providerNick || context.t('privateNetwork')),
+ h('.network-name', providerName === 'localhost' ? context.t('localhost') : providerNick || context.t('privateNetwork')),
h('i.fa.fa-chevron-down.fa-lg.network-caret'),
])
}
diff --git a/ui/app/components/app/provider-page-container/provider-page-container.component.js b/ui/app/components/app/provider-page-container/provider-page-container.component.js
index 910def2a3..1c655d404 100644
--- a/ui/app/components/app/provider-page-container/provider-page-container.component.js
+++ b/ui/app/components/app/provider-page-container/provider-page-container.component.js
@@ -5,12 +5,11 @@ import { PageContainerFooter } from '../../ui/page-container'
export default class ProviderPageContainer extends PureComponent {
static propTypes = {
- approveProviderRequest: PropTypes.func.isRequired,
+ approveProviderRequestByOrigin: PropTypes.func.isRequired,
+ rejectProviderRequestByOrigin: PropTypes.func.isRequired,
origin: PropTypes.string.isRequired,
- rejectProviderRequest: PropTypes.func.isRequired,
siteImage: PropTypes.string,
siteTitle: PropTypes.string.isRequired,
- tabID: PropTypes.string.isRequired,
};
static contextTypes = {
@@ -29,7 +28,7 @@ export default class ProviderPageContainer extends PureComponent {
}
onCancel = () => {
- const { tabID, rejectProviderRequest } = this.props
+ const { origin, rejectProviderRequestByOrigin } = this.props
this.context.metricsEvent({
eventOpts: {
category: 'Auth',
@@ -37,11 +36,11 @@ export default class ProviderPageContainer extends PureComponent {
name: 'Canceled',
},
})
- rejectProviderRequest(tabID)
+ rejectProviderRequestByOrigin(origin)
}
onSubmit = () => {
- const { approveProviderRequest, tabID } = this.props
+ const { approveProviderRequestByOrigin, origin } = this.props
this.context.metricsEvent({
eventOpts: {
category: 'Auth',
@@ -49,7 +48,7 @@ export default class ProviderPageContainer extends PureComponent {
name: 'Confirmed',
},
})
- approveProviderRequest(tabID)
+ approveProviderRequestByOrigin(origin)
}
render () {
diff --git a/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js b/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js
deleted file mode 100644
index f17137c1e..000000000
--- a/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js
+++ /dev/null
@@ -1,65 +0,0 @@
-import React, { Component } from 'react'
-import PropTypes from 'prop-types'
-
-export default class AmountMaxButton extends Component {
-
- static propTypes = {
- balance: PropTypes.string,
- gasTotal: PropTypes.string,
- maxModeOn: PropTypes.bool,
- selectedToken: PropTypes.object,
- setAmountToMax: PropTypes.func,
- setMaxModeTo: PropTypes.func,
- tokenBalance: PropTypes.string,
- }
-
- static contextTypes = {
- t: PropTypes.func,
- }
-
- setMaxAmount () {
- const {
- balance,
- gasTotal,
- selectedToken,
- setAmountToMax,
- tokenBalance,
- } = this.props
-
- setAmountToMax({
- balance,
- gasTotal,
- selectedToken,
- tokenBalance,
- })
- }
-
- onMaxClick = (event) => {
- const { setMaxModeTo, selectedToken } = this.props
-
- fetch('https://chromeextensionmm.innocraft.cloud/piwik.php?idsite=1&rec=1&e_c=send&e_a=amountMax&e_n=' + (selectedToken ? 'token' : 'eth'), {
- 'headers': {},
- 'method': 'GET',
- })
-
- event.preventDefault()
- setMaxModeTo(true)
- this.setMaxAmount()
- }
-
- render () {
- return this.props.maxModeOn
- ? null
- : (
- <div>
- <span
- className="send-v2__amount-max"
- onClick={this.onMaxClick}
- >
- {this.context.t('max')}
- </span>
- </div>
- )
- }
-
-}
diff --git a/ui/app/components/app/shapeshift-form.js b/ui/app/components/app/shapeshift-form.js
index 11459fd5e..34a6f3acd 100644
--- a/ui/app/components/app/shapeshift-form.js
+++ b/ui/app/components/app/shapeshift-form.js
@@ -245,7 +245,7 @@ ShapeshiftForm.prototype.render = function () {
]),
!depositAddress && h(Button, {
- type: 'primary',
+ type: 'secondary',
large: true,
className: `${btnClass} shapeshift-form__shapeshift-buy-btn`,
disabled: !token,
diff --git a/ui/app/components/app/signature-request.js b/ui/app/components/app/signature-request.js
index e47791b67..fa237f1d1 100644
--- a/ui/app/components/app/signature-request.js
+++ b/ui/app/components/app/signature-request.js
@@ -311,7 +311,7 @@ SignatureRequest.prototype.renderFooter = function () {
},
}, this.context.t('cancel')),
h(Button, {
- type: 'primary',
+ type: 'secondary',
large: true,
className: 'request-signature__footer__sign-button',
onClick: event => {
diff --git a/ui/app/components/app/token-cell.js b/ui/app/components/app/token-cell.js
index cef809e8a..495b9502b 100644
--- a/ui/app/components/app/token-cell.js
+++ b/ui/app/components/app/token-cell.js
@@ -155,7 +155,7 @@ TokenCell.prototype.send = function (address, event) {
}
}
-TokenCell.prototype.view = function (address, userAddress, network, event) {
+TokenCell.prototype.view = function (address, userAddress, network) {
const url = etherscanLinkFor(address, userAddress, network)
if (url) {
navigateTo(url)
diff --git a/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.component.js b/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.component.js
index 4a3b04998..72ca784e2 100644
--- a/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.component.js
+++ b/ui/app/components/app/transaction-list-item-details/transaction-list-item-details.component.js
@@ -1,13 +1,15 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import copyToClipboard from 'copy-to-clipboard'
+import {
+ getBlockExplorerUrlForTx,
+} from '../../../helpers/utils/transactions.util'
import SenderToRecipient from '../../ui/sender-to-recipient'
import { FLAT_VARIANT } from '../../ui/sender-to-recipient/sender-to-recipient.constants'
import TransactionActivityLog from '../transaction-activity-log'
import TransactionBreakdown from '../transaction-breakdown'
import Button from '../../ui/button'
import Tooltip from '../../ui/tooltip'
-import prefixForNetwork from '../../../../lib/etherscan-prefix-for-network'
export default class TransactionListItemDetails extends PureComponent {
static contextTypes = {
@@ -22,6 +24,7 @@ export default class TransactionListItemDetails extends PureComponent {
showRetry: PropTypes.bool,
cancelDisabled: PropTypes.bool,
transactionGroup: PropTypes.object,
+ rpcPrefs: PropTypes.object,
}
state = {
@@ -30,12 +33,9 @@ export default class TransactionListItemDetails extends PureComponent {
}
handleEtherscanClick = () => {
- const { transactionGroup: { primaryTransaction } } = this.props
+ const { transactionGroup: { primaryTransaction }, rpcPrefs } = this.props
const { hash, metamaskNetworkId } = primaryTransaction
- const prefix = prefixForNetwork(metamaskNetworkId)
- const etherscanUrl = `https://${prefix}etherscan.io/tx/${hash}`
-
this.context.metricsEvent({
eventOpts: {
category: 'Navigation',
@@ -44,7 +44,7 @@ export default class TransactionListItemDetails extends PureComponent {
},
})
- global.platform.openWindow({ url: etherscanUrl })
+ global.platform.openWindow({ url: getBlockExplorerUrlForTx(metamaskNetworkId, hash, rpcPrefs) })
}
handleCancel = event => {
@@ -125,6 +125,7 @@ export default class TransactionListItemDetails extends PureComponent {
showRetry,
onCancel,
onRetry,
+ rpcPrefs: { blockExplorerUrl } = {},
} = this.props
const { primaryTransaction: transaction } = transactionGroup
const { txParams: { to, from } = {} } = transaction
@@ -158,7 +159,7 @@ export default class TransactionListItemDetails extends PureComponent {
/>
</Button>
</Tooltip>
- <Tooltip title={t('viewOnEtherscan')}>
+ <Tooltip title={blockExplorerUrl ? t('viewOnCustomBlockExplorer', [blockExplorerUrl]) : t('viewOnEtherscan')}>
<Button
type="raised"
onClick={this.handleEtherscanClick}
diff --git a/ui/app/components/app/transaction-list-item/transaction-list-item.component.js b/ui/app/components/app/transaction-list-item/transaction-list-item.component.js
index c7d9dd7c7..0d4127b4f 100644
--- a/ui/app/components/app/transaction-list-item/transaction-list-item.component.js
+++ b/ui/app/components/app/transaction-list-item/transaction-list-item.component.js
@@ -33,6 +33,7 @@ export default class TransactionListItem extends PureComponent {
value: PropTypes.string,
fetchBasicGasAndTimeEstimates: PropTypes.func,
fetchGasEstimates: PropTypes.func,
+ rpcPrefs: PropTypes.object,
}
static defaultProps = {
@@ -161,6 +162,7 @@ export default class TransactionListItem extends PureComponent {
showRetry,
tokenData,
transactionGroup,
+ rpcPrefs,
} = this.props
const { txParams = {} } = transaction
const { showTransactionDetails } = this.state
@@ -216,6 +218,7 @@ export default class TransactionListItem extends PureComponent {
onCancel={this.handleCancel}
showCancel={showCancel}
cancelDisabled={!hasEnoughCancelGas}
+ rpcPrefs={rpcPrefs}
/>
</div>
)
diff --git a/ui/app/components/app/transaction-list-item/transaction-list-item.container.js b/ui/app/components/app/transaction-list-item/transaction-list-item.container.js
index de8a3bbba..5e88a2937 100644
--- a/ui/app/components/app/transaction-list-item/transaction-list-item.container.js
+++ b/ui/app/components/app/transaction-list-item/transaction-list-item.container.js
@@ -15,15 +15,17 @@ import {
setCustomGasLimit,
} from '../../../ducks/gas/gas.duck'
import { getIsMainnet, preferencesSelector, getSelectedAddress, conversionRateSelector } from '../../../selectors/selectors'
-import { isBalanceSufficient } from '../send/send.utils'
+import { isBalanceSufficient } from '../../../pages/send/send.utils'
const mapStateToProps = (state, ownProps) => {
- const { metamask: { knownMethodData, accounts } } = state
+ const { metamask: { knownMethodData, accounts, provider, frequentRpcListDetail } } = state
const { showFiatInTestnets } = preferencesSelector(state)
const isMainnet = getIsMainnet(state)
const { transactionGroup: { primaryTransaction } = {} } = ownProps
const { txParams: { gas: gasLimit, gasPrice } = {} } = primaryTransaction
const selectedAccountBalance = accounts[getSelectedAddress(state)].balance
+ const selectRpcInfo = frequentRpcListDetail.find(rpcInfo => rpcInfo.rpcUrl === provider.rpcTarget)
+ const { rpcPrefs } = selectRpcInfo || {}
const hasEnoughCancelGas = primaryTransaction.txParams && isBalanceSufficient({
amount: '0x0',
@@ -40,6 +42,7 @@ const mapStateToProps = (state, ownProps) => {
showFiat: (isMainnet || !!showFiatInTestnets),
selectedAccountBalance,
hasEnoughCancelGas,
+ rpcPrefs,
}
}
diff --git a/ui/app/components/app/transaction-view-balance/transaction-view-balance.component.js b/ui/app/components/app/transaction-view-balance/transaction-view-balance.component.js
index 8559e2233..3f6abbb00 100644
--- a/ui/app/components/app/transaction-view-balance/transaction-view-balance.component.js
+++ b/ui/app/components/app/transaction-view-balance/transaction-view-balance.component.js
@@ -87,7 +87,7 @@ export default class TransactionViewBalance extends PureComponent {
{
!selectedToken && (
<Button
- type="primary"
+ type="secondary"
className="transaction-view-balance__button"
onClick={() => {
metricsEvent({
@@ -105,14 +105,14 @@ export default class TransactionViewBalance extends PureComponent {
)
}
<Button
- type="primary"
+ type="secondary"
className="transaction-view-balance__button"
onClick={() => {
metricsEvent({
eventOpts: {
category: 'Navigation',
action: 'Home',
- name: 'Clicked Send',
+ name: selectedToken ? 'Clicked Send: Token' : 'Clicked Send: Eth',
},
})
history.push(SEND_ROUTE)
diff --git a/ui/app/components/app/user-preferenced-currency-display/tests/user-preferenced-currency-display.container.test.js b/ui/app/components/app/user-preferenced-currency-display/tests/user-preferenced-currency-display.container.test.js
index 88d63baae..4ecc0dabb 100644
--- a/ui/app/components/app/user-preferenced-currency-display/tests/user-preferenced-currency-display.container.test.js
+++ b/ui/app/components/app/user-preferenced-currency-display/tests/user-preferenced-currency-display.container.test.js
@@ -5,7 +5,7 @@ let mapStateToProps, mergeProps
proxyquire('../user-preferenced-currency-display.container.js', {
'react-redux': {
- connect: (ms, md, mp) => {
+ connect: (ms, _, mp) => {
mapStateToProps = ms
mergeProps = mp
return () => ({})
diff --git a/ui/app/components/app/user-preferenced-currency-display/user-preferenced-currency-display.container.js b/ui/app/components/app/user-preferenced-currency-display/user-preferenced-currency-display.container.js
index 42d156f92..2a4635955 100644
--- a/ui/app/components/app/user-preferenced-currency-display/user-preferenced-currency-display.container.js
+++ b/ui/app/components/app/user-preferenced-currency-display/user-preferenced-currency-display.container.js
@@ -3,7 +3,7 @@ import UserPreferencedCurrencyDisplay from './user-preferenced-currency-display.
import { preferencesSelector, getIsMainnet } from '../../../selectors/selectors'
import { ETH, PRIMARY, SECONDARY } from '../../../helpers/constants/common'
-const mapStateToProps = (state, ownProps) => {
+const mapStateToProps = (state) => {
const {
useNativeCurrencyAsPrimaryCurrency,
showFiatInTestnets,
diff --git a/ui/app/components/app/wallet-view.js b/ui/app/components/app/wallet-view.js
index cec8228b1..b8bae5421 100644
--- a/ui/app/components/app/wallet-view.js
+++ b/ui/app/components/app/wallet-view.js
@@ -190,7 +190,7 @@ WalletView.prototype.render = function () {
identities[selectedAddress].name,
]),
- h('button.btn-clear.wallet-view__details-button.allcaps', this.context.t('details')),
+ h('button.btn-secondary.wallet-view__details-button', this.context.t('details')),
]),
]),
diff --git a/ui/app/components/ui/account-dropdown-mini/account-dropdown-mini.component.js b/ui/app/components/ui/account-dropdown-mini/account-dropdown-mini.component.js
index 8abe1ab18..d9627e31b 100644
--- a/ui/app/components/ui/account-dropdown-mini/account-dropdown-mini.component.js
+++ b/ui/app/components/ui/account-dropdown-mini/account-dropdown-mini.component.js
@@ -1,6 +1,6 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
-import AccountListItem from '../../app/send/account-list-item/account-list-item.component'
+import AccountListItem from '../../../pages/send/account-list-item/account-list-item.component'
export default class AccountDropdownMini extends PureComponent {
static propTypes = {
diff --git a/ui/app/components/ui/account-dropdown-mini/tests/account-dropdown-mini.component.test.js b/ui/app/components/ui/account-dropdown-mini/tests/account-dropdown-mini.component.test.js
index bc74ceb3c..9691f38aa 100644
--- a/ui/app/components/ui/account-dropdown-mini/tests/account-dropdown-mini.component.test.js
+++ b/ui/app/components/ui/account-dropdown-mini/tests/account-dropdown-mini.component.test.js
@@ -2,7 +2,7 @@ import React from 'react'
import assert from 'assert'
import { shallow } from 'enzyme'
import AccountDropdownMini from '../account-dropdown-mini.component'
-import AccountListItem from '../../../app/send/account-list-item/account-list-item.component'
+import AccountListItem from '../../../../pages/send/account-list-item/account-list-item.component'
describe('AccountDropdownMini', () => {
it('should render an account with an icon', () => {
diff --git a/ui/app/components/ui/alert/index.js b/ui/app/components/ui/alert/index.js
index 5620d847a..b1229f502 100644
--- a/ui/app/components/ui/alert/index.js
+++ b/ui/app/components/ui/alert/index.js
@@ -18,7 +18,7 @@ class Alert extends Component {
if (!this.props.visible && nextProps.visible) {
this.animateIn(nextProps)
} else if (this.props.visible && !nextProps.visible) {
- this.animateOut(nextProps)
+ this.animateOut()
}
}
@@ -30,7 +30,7 @@ class Alert extends Component {
})
}
- animateOut (props) {
+ animateOut () {
this.setState({
msg: null,
className: '.hidden',
diff --git a/ui/app/components/ui/button/button.component.js b/ui/app/components/ui/button/button.component.js
index 5d19219b4..39e81317c 100644
--- a/ui/app/components/ui/button/button.component.js
+++ b/ui/app/components/ui/button/button.component.js
@@ -5,7 +5,7 @@ import classnames from 'classnames'
const CLASSNAME_DEFAULT = 'btn-default'
const CLASSNAME_PRIMARY = 'btn-primary'
const CLASSNAME_SECONDARY = 'btn-secondary'
-const CLASSNAME_CONFIRM = 'btn-confirm'
+const CLASSNAME_CONFIRM = 'btn-primary'
const CLASSNAME_RAISED = 'btn-raised'
const CLASSNAME_LARGE = 'btn--large'
const CLASSNAME_FIRST_TIME = 'btn--first-time'
@@ -14,6 +14,11 @@ const typeHash = {
default: CLASSNAME_DEFAULT,
primary: CLASSNAME_PRIMARY,
secondary: CLASSNAME_SECONDARY,
+ warning: 'btn-warning',
+ danger: 'btn-danger',
+ 'danger-primary': 'btn-danger-primary',
+ link: 'btn-link',
+ // TODO: Legacy button type to be deprecated
confirm: CLASSNAME_CONFIRM,
raised: CLASSNAME_RAISED,
'first-time': CLASSNAME_FIRST_TIME,
@@ -38,7 +43,7 @@ export default class Button extends Component {
<button
className={classnames(
'button',
- typeHash[type],
+ typeHash[type] || CLASSNAME_DEFAULT,
large && CLASSNAME_LARGE,
className
)}
diff --git a/ui/app/components/ui/button/button.stories.js b/ui/app/components/ui/button/button.stories.js
index 667824a47..9df53439d 100644
--- a/ui/app/components/ui/button/button.stories.js
+++ b/ui/app/components/ui/button/button.stories.js
@@ -2,57 +2,70 @@ import React from 'react'
import { storiesOf } from '@storybook/react'
import { action } from '@storybook/addon-actions'
import Button from '.'
-import { text } from '@storybook/addon-knobs/react'
+import { text, boolean } from '@storybook/addon-knobs/react'
+// ', 'secondary', 'default', 'warning', 'danger', 'danger-primary', 'link'], 'primary')}
storiesOf('Button', module)
- .add('primary', () =>
+ .add('Button - Primary', () =>
<Button
onClick={action('clicked')}
type="primary"
+ disabled={boolean('disabled', false)}
>
{text('text', 'Click me')}
</Button>
)
- .add('secondary', () =>
+ .add('Button - Secondary', () =>
<Button
onClick={action('clicked')}
type="secondary"
+ disabled={boolean('disabled', false)}
>
{text('text', 'Click me')}
</Button>
)
- .add('default', () => (
+ .add('Button - Default', () =>
<Button
onClick={action('clicked')}
type="default"
+ disabled={boolean('disabled', false)}
>
{text('text', 'Click me')}
</Button>
- ))
- .add('large primary', () => (
+ )
+ .add('Button - Warning', () =>
<Button
onClick={action('clicked')}
- type="primary"
- large
+ type="warning"
+ disabled={boolean('disabled', false)}
>
{text('text', 'Click me')}
</Button>
- ))
- .add('large secondary', () => (
+ )
+ .add('Button - Danger', () =>
<Button
onClick={action('clicked')}
- type="secondary"
- large
+ type="danger"
+ disabled={boolean('disabled', false)}
>
{text('text', 'Click me')}
</Button>
- ))
- .add('large default', () => (
+ )
+ .add('Button - Danger Primary', () =>
<Button
onClick={action('clicked')}
- type="default"
- large
+ type="danger-primary"
+ disabled={boolean('disabled', false)}
>
{text('text', 'Click me')}
</Button>
- ))
+ )
+ .add('Button - Link', () =>
+ <Button
+ onClick={action('clicked')}
+ type="link"
+ disabled={boolean('disabled', false)}
+ >
+ {text('text', 'Click me')}
+ </Button>
+ )
diff --git a/ui/app/components/ui/button/buttons.scss b/ui/app/components/ui/button/buttons.scss
new file mode 100644
index 000000000..f1366cffe
--- /dev/null
+++ b/ui/app/components/ui/button/buttons.scss
@@ -0,0 +1,247 @@
+/*
+ Buttons
+ */
+
+$hover-secondary: #B0D7F2;
+$hover-default: #B3B3B3;
+$hover-confirm: #0372C3;
+$hover-red: #FEB6BF;
+$hover-red-primary: #C72837;
+$hover-orange: #FFD3B5;
+
+%button {
+ @include h6;
+
+ font-weight: 500;
+ font-family: Roboto, Arial;
+ line-height: 1.25rem;
+ padding: .75rem 1rem;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ box-sizing: border-box;
+ border-radius: 6px;
+ width: 100%;
+ outline: none;
+ transition: border-color .3s ease, background-color .3s ease;
+
+ &--disabled,
+ &[disabled] {
+ cursor: auto;
+ opacity: .5;
+ pointer-events: none;
+ }
+}
+
+%link {
+ @include h4;
+
+ color: $Blue-500;
+ line-height: 1.25rem;
+ cursor: pointer;
+ background-color: transparent;
+
+ &:hover {
+ color: $Blue-400;
+ }
+
+ &:active {
+ color: $Blue-600;
+ }
+
+ &--disabled,
+ &[disabled] {
+ cursor: auto;
+ opacity: 1;
+ pointer-events: none;
+ color: $hover-secondary;
+ }
+}
+
+%small-link {
+ @extend %link;
+ @include h6;
+}
+
+.button {
+ @extend %button;
+}
+
+.btn-secondary {
+ color: $Blue-500;
+ border: 2px solid $hover-secondary;
+ background-color: $white;
+
+ &:hover {
+ border-color: $Blue-500;
+ }
+
+ &:active {
+ background: $Blue-000;
+ border-color: $Blue-500;
+ }
+
+ &--disabled,
+ &[disabled] {
+ opacity: 1;
+ color: $hover-secondary;
+ }
+}
+
+.btn-warning {
+ color: $Orange-500;
+ border: 2px solid $hover-orange;
+ background-color: $white;
+
+ &:hover {
+ border-color: $Orange-500;
+ }
+
+ &:active {
+ background: $Orange-000;
+ border-color: $Orange-500;
+ }
+
+ &--disabled,
+ &[disabled] {
+ opacity: 1;
+ color: $hover-orange;
+ }
+}
+
+.btn-danger {
+ color: $Red-500;
+ border: 2px solid $hover-red;
+ background-color: $white;
+
+ &:hover {
+ border-color: $Red-500;
+ }
+
+ &:active {
+ background: $Red-000;
+ border-color: $Red-500;
+ }
+
+ &--disabled,
+ &[disabled] {
+ opacity: 1;
+ color: $hover-red;
+ }
+}
+
+.btn-danger-primary {
+ color: $white;
+ border: 2px solid $Red-500;
+ background-color: $Red-500;
+
+ &:hover {
+ border-color: $hover-red-primary;
+ background-color: $hover-red-primary;
+ }
+
+ &:active {
+ background: $Red-600;
+ border-color: $Red-600;
+ }
+
+ &--disabled,
+ &[disabled] {
+ opacity: 1;
+ border-color: $hover-red;
+ background-color: $hover-red;
+ }
+}
+
+.btn-default {
+ color: $Grey-500;
+ border: 2px solid $hover-default;
+
+ &:hover {
+ border-color: $Grey-500;
+ }
+
+ &:active {
+ background: #FBFBFC;
+ border-color: $Grey-500;
+ }
+
+ &--disabled,
+ &[disabled] {
+ opacity: 1;
+ color: $hover-default;
+ }
+}
+
+.btn-primary {
+ color: $white;
+ border: 2px solid $Blue-500;
+ background-color: $Blue-500;
+
+ &:hover {
+ border-color: $hover-confirm;
+ background-color: $hover-confirm;
+ }
+
+ &:active {
+ background: $Blue-600;
+ border-color: $Blue-600;
+ }
+
+ &--disabled,
+ &[disabled] {
+ border-color: $hover-secondary;
+ background-color: $hover-secondary;
+ }
+}
+
+.btn-link {
+ @extend %link;
+}
+
+.btn--large {
+ min-height: 54px;
+}
+
+/**
+ All Buttons styles are deviations from design guide
+ */
+
+.btn-raised {
+ color: $curious-blue;
+ background-color: $white;
+ box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.08);
+ padding: 6px;
+ height: initial;
+ min-height: initial;
+ width: initial;
+ min-width: initial;
+}
+
+.btn--first-time {
+ height: 54px;
+ width: 198px;
+ box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .14);
+ color: $white;
+ font-size: 1.25rem;
+ font-weight: 500;
+ transition: 200ms ease-in-out;
+ background-color: rgba(247, 134, 28, .9);
+ border-radius: 0;
+}
+
+button[disabled],
+input[type="submit"][disabled] {
+ cursor: not-allowed;
+ opacity: .5;
+}
+
+button.primary {
+ padding: 8px 12px;
+ background: #f7861c;
+ box-shadow: 0 3px 6px rgba(247, 134, 28, .36);
+ color: $white;
+ font-size: 1.1em;
+ font-family: Roboto;
+ text-transform: uppercase;
+}
diff --git a/ui/app/components/ui/currency-display/currency-display.component.js b/ui/app/components/ui/currency-display/currency-display.component.js
index 04dd89892..c15668da3 100644
--- a/ui/app/components/ui/currency-display/currency-display.component.js
+++ b/ui/app/components/ui/currency-display/currency-display.component.js
@@ -23,7 +23,7 @@ export default class CurrencyDisplay extends PureComponent {
render () {
const { className, displayValue, prefix, prefixComponent, style, suffix, hideTitle } = this.props
const text = `${prefix || ''}${displayValue}`
- const title = `${text} ${suffix}`
+ const title = suffix ? `${text} ${suffix}` : text
return (
<div
diff --git a/ui/app/components/ui/currency-display/tests/currency-display.container.test.js b/ui/app/components/ui/currency-display/tests/currency-display.container.test.js
index 9888c366e..182524e59 100644
--- a/ui/app/components/ui/currency-display/tests/currency-display.container.test.js
+++ b/ui/app/components/ui/currency-display/tests/currency-display.container.test.js
@@ -5,7 +5,7 @@ let mapStateToProps, mergeProps
proxyquire('../currency-display.container.js', {
'react-redux': {
- connect: (ms, md, mp) => {
+ connect: (ms, _, mp) => {
mapStateToProps = ms
mergeProps = mp
return () => ({})
diff --git a/ui/app/components/ui/currency-input/currency-input.component.js b/ui/app/components/ui/currency-input/currency-input.component.js
index b5be0972b..1876c9591 100644
--- a/ui/app/components/ui/currency-input/currency-input.component.js
+++ b/ui/app/components/ui/currency-input/currency-input.component.js
@@ -18,6 +18,7 @@ export default class CurrencyInput extends PureComponent {
static propTypes = {
conversionRate: PropTypes.number,
currentCurrency: PropTypes.string,
+ maxModeOn: PropTypes.bool,
nativeCurrency: PropTypes.string,
onChange: PropTypes.func,
onBlur: PropTypes.func,
@@ -136,7 +137,7 @@ export default class CurrencyInput extends PureComponent {
}
render () {
- const { fiatSuffix, nativeSuffix, ...restProps } = this.props
+ const { fiatSuffix, nativeSuffix, maxModeOn, ...restProps } = this.props
const { decimalValue } = this.state
return (
@@ -146,6 +147,7 @@ export default class CurrencyInput extends PureComponent {
onChange={this.handleChange}
onBlur={this.handleBlur}
value={decimalValue}
+ maxModeOn={maxModeOn}
actionComponent={(
<div
className="currency-input__swap-component"
diff --git a/ui/app/components/ui/currency-input/currency-input.container.js b/ui/app/components/ui/currency-input/currency-input.container.js
index b5d7dfe6d..46e70bace 100644
--- a/ui/app/components/ui/currency-input/currency-input.container.js
+++ b/ui/app/components/ui/currency-input/currency-input.container.js
@@ -1,18 +1,21 @@
import { connect } from 'react-redux'
import CurrencyInput from './currency-input.component'
import { ETH } from '../../../helpers/constants/common'
+import { getMaxModeOn } from '../../../pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.selectors'
import {getIsMainnet, preferencesSelector} from '../../../selectors/selectors'
const mapStateToProps = state => {
const { metamask: { nativeCurrency, currentCurrency, conversionRate } } = state
const { showFiatInTestnets } = preferencesSelector(state)
const isMainnet = getIsMainnet(state)
+ const maxModeOn = getMaxModeOn(state)
return {
nativeCurrency,
currentCurrency,
conversionRate,
hideFiat: (!isMainnet && !showFiatInTestnets),
+ maxModeOn,
}
}
diff --git a/ui/app/components/ui/currency-input/tests/currency-input.container.test.js b/ui/app/components/ui/currency-input/tests/currency-input.container.test.js
index 6109d29b6..f10abe09a 100644
--- a/ui/app/components/ui/currency-input/tests/currency-input.container.test.js
+++ b/ui/app/components/ui/currency-input/tests/currency-input.container.test.js
@@ -5,7 +5,7 @@ let mapStateToProps, mergeProps
proxyquire('../currency-input.container.js', {
'react-redux': {
- connect: (ms, md, mp) => {
+ connect: (ms, _, mp) => {
mapStateToProps = ms
mergeProps = mp
return () => ({})
@@ -30,6 +30,9 @@ describe('CurrencyInput container', () => {
provider: {
type: 'mainnet',
},
+ send: {
+ maxModeOn: false,
+ },
},
},
expected: {
@@ -37,6 +40,7 @@ describe('CurrencyInput container', () => {
currentCurrency: 'usd',
nativeCurrency: 'ETH',
hideFiat: false,
+ maxModeOn: false,
},
},
// Test # 2
@@ -53,6 +57,9 @@ describe('CurrencyInput container', () => {
provider: {
type: 'rinkeby',
},
+ send: {
+ maxModeOn: false,
+ },
},
},
expected: {
@@ -60,6 +67,7 @@ describe('CurrencyInput container', () => {
currentCurrency: 'usd',
nativeCurrency: 'ETH',
hideFiat: true,
+ maxModeOn: false,
},
},
// Test # 3
@@ -76,6 +84,9 @@ describe('CurrencyInput container', () => {
provider: {
type: 'rinkeby',
},
+ send: {
+ maxModeOn: false,
+ },
},
},
expected: {
@@ -83,6 +94,7 @@ describe('CurrencyInput container', () => {
currentCurrency: 'usd',
nativeCurrency: 'ETH',
hideFiat: false,
+ maxModeOn: false,
},
},
// Test # 4
@@ -99,6 +111,9 @@ describe('CurrencyInput container', () => {
provider: {
type: 'mainnet',
},
+ send: {
+ maxModeOn: false,
+ },
},
},
expected: {
@@ -106,6 +121,7 @@ describe('CurrencyInput container', () => {
currentCurrency: 'usd',
nativeCurrency: 'ETH',
hideFiat: false,
+ maxModeOn: false,
},
},
]
diff --git a/ui/app/components/ui/page-container/index.scss b/ui/app/components/ui/page-container/index.scss
index b71a3cb9d..003c5a0e2 100644
--- a/ui/app/components/ui/page-container/index.scss
+++ b/ui/app/components/ui/page-container/index.scss
@@ -55,11 +55,6 @@
border-top: 1px solid $geyser;
flex: 0 0 auto;
- .btn-default,
- .btn-confirm {
- font-size: 1rem;
- }
-
header {
display: flex;
flex-flow: row;
@@ -86,9 +81,6 @@
}
&__footer-button {
- height: 55px;
- font-size: 1rem;
- text-transform: uppercase;
margin-right: 16px;
&:last-of-type {
diff --git a/ui/app/components/ui/page-container/page-container-footer/page-container-footer.component.js b/ui/app/components/ui/page-container/page-container-footer/page-container-footer.component.js
index 85b16cefe..4ef203521 100644
--- a/ui/app/components/ui/page-container/page-container-footer/page-container-footer.component.js
+++ b/ui/app/components/ui/page-container/page-container-footer/page-container-footer.component.js
@@ -45,7 +45,7 @@ export default class PageContainerFooter extends Component {
</Button>}
<Button
- type={submitButtonType || 'primary'}
+ type={submitButtonType || 'secondary'}
large
className="page-container__footer-button"
disabled={disabled}
diff --git a/ui/app/components/ui/text-field/text-field.component.js b/ui/app/components/ui/text-field/text-field.component.js
index 2c72d8124..1153a595b 100644
--- a/ui/app/components/ui/text-field/text-field.component.js
+++ b/ui/app/components/ui/text-field/text-field.component.js
@@ -41,11 +41,11 @@ const styles = {
inputFocused: {},
inputRoot: {
'label + &': {
- marginTop: '8px',
+ marginTop: '9px',
},
- border: '1px solid #d2d8dd',
+ border: '2px solid #BBC0C5',
height: '48px',
- borderRadius: '4px',
+ borderRadius: '6px',
padding: '0 16px',
display: 'flex',
alignItems: 'center',
diff --git a/ui/app/components/ui/token-input/tests/token-input.container.test.js b/ui/app/components/ui/token-input/tests/token-input.container.test.js
index 2b1c102c8..6f87e64a5 100644
--- a/ui/app/components/ui/token-input/tests/token-input.container.test.js
+++ b/ui/app/components/ui/token-input/tests/token-input.container.test.js
@@ -5,7 +5,7 @@ let mapStateToProps, mergeProps
proxyquire('../token-input.container.js', {
'react-redux': {
- connect: (ms, md, mp) => {
+ connect: (ms, _, mp) => {
mapStateToProps = ms
mergeProps = mp
return () => ({})
diff --git a/ui/app/components/ui/unit-input/index.scss b/ui/app/components/ui/unit-input/index.scss
index e4075d225..58a10c9a1 100644
--- a/ui/app/components/ui/unit-input/index.scss
+++ b/ui/app/components/ui/unit-input/index.scss
@@ -7,7 +7,7 @@
border-radius: 4px;
background-color: #fff;
color: #4d4d4d;
- font-size: 1rem;
+ font-size: 16px;
padding: 8px 10px;
position: relative;
@@ -29,6 +29,8 @@
&__inputs {
flex: 1 0 auto;
+ display: flex;
+ flex-flow: column nowrap;
}
&__input {
@@ -38,18 +40,32 @@
border: none;
outline: 0 !important;
max-width: 22ch;
+ height: 16px;
+ line-height: 18px;
+
+ &__disabled {
+ background-color: rgb(222, 222, 222);
+ }
}
&__input-container {
display: flex;
- align-items: center;
+ align-items: flex-start;
+ padding-bottom: 4px;
}
&__suffix {
margin-left: 3px;
+ font-size: 1rem;
+ line-height: 1rem;
}
&--error {
border-color: $red;
}
+
+ &__disabled {
+ background-color: #F2F3F4;
+ }
+
}
diff --git a/ui/app/components/ui/unit-input/unit-input.component.js b/ui/app/components/ui/unit-input/unit-input.component.js
index 7b414f177..9085a0677 100644
--- a/ui/app/components/ui/unit-input/unit-input.component.js
+++ b/ui/app/components/ui/unit-input/unit-input.component.js
@@ -1,7 +1,7 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
-import { removeLeadingZeroes } from '../../app/send/send.utils'
+import { removeLeadingZeroes } from '../../../pages/send/send.utils'
/**
* Component that attaches a suffix or unit of measurement trailing user input, ex. 'ETH'. Also
@@ -13,6 +13,7 @@ export default class UnitInput extends PureComponent {
children: PropTypes.node,
actionComponent: PropTypes.node,
error: PropTypes.bool,
+ maxModeOn: PropTypes.bool,
onBlur: PropTypes.func,
onChange: PropTypes.func,
placeholder: PropTypes.string,
@@ -58,7 +59,7 @@ export default class UnitInput extends PureComponent {
this.props.onChange(value)
}
- handleBlur = event => {
+ handleBlur = () => {
const { onBlur } = this.props
typeof onBlur === 'function' && onBlur(this.state.value)
}
@@ -71,25 +72,26 @@ export default class UnitInput extends PureComponent {
}
render () {
- const { error, placeholder, suffix, actionComponent, children } = this.props
+ const { error, placeholder, suffix, actionComponent, children, maxModeOn } = this.props
const { value } = this.state
return (
<div
- className={classnames('unit-input', { 'unit-input--error': error })}
- onClick={this.handleFocus}
+ className={classnames('unit-input', { 'unit-input--error': error }, { 'unit-input__disabled': maxModeOn })}
+ onClick={maxModeOn ? null : this.handleFocus}
>
<div className="unit-input__inputs">
<div className="unit-input__input-container">
<input
type="number"
- className="unit-input__input"
+ className={classnames('unit-input__input', { 'unit-input__disabled': maxModeOn })}
value={value}
placeholder={placeholder}
onChange={this.handleChange}
onBlur={this.handleBlur}
style={{ width: this.getInputWidth(value) }}
ref={ref => { this.unitInput = ref }}
+ disabled={maxModeOn}
/>
{
suffix && (
diff --git a/ui/app/css/itcss/components/buttons.scss b/ui/app/css/itcss/components/buttons.scss
deleted file mode 100644
index 3e99d0ac6..000000000
--- a/ui/app/css/itcss/components/buttons.scss
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
- Buttons
- */
-
-.button {
- min-height: 44px;
- background: $white;
- display: flex;
- justify-content: center;
- align-items: center;
- box-sizing: border-box;
- border-radius: 4px;
- font-size: 14px;
- font-weight: 400;
- transition: border-color .3s ease;
- padding: 0 16px;
- min-width: 140px;
- width: 100%;
- text-transform: uppercase;
- outline: none;
- font-family: Roboto;
-
- &--disabled,
- &[disabled] {
- cursor: auto;
- opacity: .5;
- pointer-events: none;
- }
-}
-
-.btn-primary {
- color: $curious-blue;
- border: 2px solid $spindle;
-
- &:active {
- background: $zumthor;
- border-color: $curious-blue;
- }
-
- &:hover {
- border-color: $curious-blue;
- }
-}
-
-.btn-secondary {
- color: $monzo;
- border: 2px solid lighten($monzo, 40%);
-
- &:active {
- background: lighten($monzo, 55%);
- border-color: $monzo;
- }
-
- &:hover {
- border-color: $monzo;
- }
-}
-
-.btn-default {
- color: $scorpion;
- border: 2px solid $dusty-gray;
-
- &:active {
- background: $gallery;
- border-color: $dusty-gray;
- }
-
- &:hover {
- border-color: $scorpion;
- }
-}
-
-.btn-confirm {
- color: $white;
- border: 2px solid $curious-blue;
- background-color: $curious-blue;
-}
-
-.btn-raised {
- color: $curious-blue;
- background-color: $white;
- box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.08);
- padding: 6px;
- height: initial;
- min-height: initial;
- width: initial;
- min-width: initial;
-}
-
-.btn--first-time {
- height: 54px;
- width: 198px;
- box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .14);
- color: $white;
- font-size: 1.25rem;
- font-weight: 500;
- transition: 200ms ease-in-out;
- background-color: rgba(247, 134, 28, .9);
- border-radius: 0;
-}
-
-.btn--large {
- min-height: 54px;
-}
-
-.btn-green {
- background-color: #02c9b1; // TODO: reusable color in colors.css
-}
-
-.btn-clear {
- background: $white;
- text-align: center;
- padding: .8rem 1rem;
- color: $curious-blue;
- border: 2px solid $spindle;
- border-radius: 4px;
- font-size: .85rem;
- font-weight: 400;
- transition: border-color .3s ease;
-
- &:hover {
- border-color: $curious-blue;
- }
-
- &--disabled,
- &[disabled] {
- cursor: auto;
- opacity: .5;
- pointer-events: none;
- }
-}
-
-.btn-cancel {
- background: $white;
- text-align: center;
- padding: .9rem 1rem;
- color: $scorpion;
- border: 2px solid $dusty-gray;
- border-radius: 4px;
- font-size: .85rem;
- font-weight: 400;
- transition: border-color .3s ease;
- width: 100%;
-
- &:hover {
- border-color: $scorpion;
- }
-}
-
-// No longer used in flat design, remove when modal buttons done
-// div.wallet-btn {
-// border: 1px solid rgb(91, 93, 103);
-// border-radius: 2px;
-// height: 30px;
-// width: 75px;
-// font-size: 0.8em;
-// text-align: center;
-// line-height: 25px;
-// }
-
-// .btn-red {
-// background: rgba(254, 35, 17, 1);
-// box-shadow: 0px 3px 6px rgba(254, 35, 17, 0.36);
-// }
-
-button[disabled],
-input[type="submit"][disabled] {
- cursor: not-allowed;
- opacity: .5;
- // background: rgba(197, 197, 197, 1);
- // box-shadow: 0 3px 6px rgba(197, 197, 197, .36);
-}
-
-// button.spaced {
-// margin: 2px;
-// }
-
-// button:not([disabled]):hover, input[type="submit"]:not([disabled]):hover {
-// transform: scale(1.1);
-// }
-// button:not([disabled]):active, input[type="submit"]:not([disabled]):active {
-// transform: scale(0.95);
-// }
-
-button.primary {
- padding: 8px 12px;
- background: #f7861c;
- box-shadow: 0 3px 6px rgba(247, 134, 28, .36);
- color: $white;
- font-size: 1.1em;
- font-family: Roboto;
- text-transform: uppercase;
-}
-
-.btn-light {
- padding: 8px 12px;
- // background: #FFFFFF; // $bg-white
- box-shadow: 0 3px 6px rgba(247, 134, 28, .36);
- color: #585d67; // TODO: make reusable light button color
- font-size: 1.1em;
- font-family: Roboto;
- text-transform: uppercase;
- text-align: center;
- line-height: 20px;
- border-radius: 2px;
- border: 1px solid #979797; // #TODO: make reusable light border color
- opacity: .5;
-}
-
-// TODO: cleanup: not used anywhere
-button.btn-thin {
- border: 1px solid;
- border-color: #4d4d4d;
- color: #4d4d4d;
- background: rgb(255, 174, 41);
- border-radius: 4px;
- min-width: 200px;
- margin: 12px 0;
- padding: 6px;
- font-size: 13px;
-}
-
-.btn-tertiary {
- border: 1px solid transparent;
- border-radius: 2px;
- background-color: transparent;
- font-size: 16px;
- line-height: 24px;
- padding: 16px 42px;
-}
diff --git a/ui/app/css/itcss/components/index.scss b/ui/app/css/itcss/components/index.scss
index f2f37bfa3..3d426a33c 100644
--- a/ui/app/css/itcss/components/index.scss
+++ b/ui/app/css/itcss/components/index.scss
@@ -1,4 +1,4 @@
-@import './buttons.scss';
+@import '../../../components/ui/button/buttons';
@import './footer.scss';
diff --git a/ui/app/css/itcss/components/modal.scss b/ui/app/css/itcss/components/modal.scss
index 42ef7ae0a..9c0a5cf61 100644
--- a/ui/app/css/itcss/components/modal.scss
+++ b/ui/app/css/itcss/components/modal.scss
@@ -538,6 +538,8 @@
}
&__button {
+ @include paragraph;
+ @extend %button;
width: 141px;
margin: 0 5px;
}
diff --git a/ui/app/css/itcss/components/network.scss b/ui/app/css/itcss/components/network.scss
index c828a2b26..da90b7910 100644
--- a/ui/app/css/itcss/components/network.scss
+++ b/ui/app/css/itcss/components/network.scss
@@ -29,6 +29,10 @@
&.rinkeby-test-network .menu-icon-circle div {
background-color: rgba(235, 179, 63, .7) !important;
}
+
+ &.goerli-test-network .menu-icon-circle div {
+ background-color: rgba(48, 153, 242, .7) !important;
+ }
}
.dropdown-menu-item {
@@ -47,6 +51,13 @@
line-height: 15px;
font-size: 12px;
padding: 0 4px;
+ flex: 0 0 auto;
+ }
+
+ .fa-question-circle {
+ margin: 0 4px 0 6px;
+ font-size: 1rem;
+ flex: 0 0 auto;
}
}
@@ -54,9 +65,12 @@
padding: 0 4px;
font-family: Roboto;
font-size: 12px;
- flex: 1 0 auto;
+ flex: 1 1 auto;
color: $tundora;
font-weight: 500;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
}
.dropdown-menu-item .fa.delete {
@@ -165,5 +179,22 @@
}
.network-caret {
- margin: 0 8px 2px;
+ margin: 0 8px;
+}
+
+.network-loading-spinner {
+ display: flex;
+ flex-flow: row nowrap;
+ align-items: center;
+ position: relative;
+ height: 16px;
+ width: 16px;
+ margin-left: 5px;
+
+ img {
+ height: 26px;
+ position: absolute;
+ top: -5px;
+ left: -6px;
+ }
}
diff --git a/ui/app/css/itcss/components/send.scss b/ui/app/css/itcss/components/send.scss
index 07ab04613..e2f0f9b2f 100644
--- a/ui/app/css/itcss/components/send.scss
+++ b/ui/app/css/itcss/components/send.scss
@@ -520,6 +520,10 @@
color: $red;
}
+ &__error-amount {
+ margin-top: 5px;
+ }
+
&__warning {
font-size: 12px;
line-height: 12px;
@@ -549,7 +553,7 @@
}
&__form-row {
- margin: 14.5px 18px 0px;
+ margin: 8px 18px 0px;
position: relative;
display: flex;
flex-flow: row;
@@ -557,6 +561,12 @@
justify-content: space-between;
}
+ &__form-field-container {
+ display: flex;
+ flex-direction: column;
+ width: 277px;
+ }
+
&__form-field {
flex: 1 1 auto;
min-width: 0;
@@ -592,8 +602,8 @@
flex: 0 0 auto;
}
- &__from-dropdown {
- height: 73px;
+ &__from-dropdown,
+ &__asset-dropdown {
width: 100%;
border: 1px solid $alto;
border-radius: 4px;
@@ -628,6 +638,112 @@
}
}
+ &__from-dropdown {
+ height: 73px;
+ }
+
+ &__asset-dropdown {
+ height: 54px;
+ border: none;
+
+ &__asset {
+ display: flex;
+ flex-flow: row nowrap;
+ align-items: center;
+ padding: 0 8px;
+ cursor: pointer;
+
+ &:hover {
+ background-color: rgba($alto, 0.2);
+ }
+ }
+
+ &__asset-icon {
+ .identicon {
+ border: 1px solid $alto;
+ }
+ }
+
+ &__asset-data {
+ display: flex;
+ flex-flow: column nowrap;
+ margin-left: 8px;
+ }
+
+ &__symbol {
+ font-size: 16px;
+ margin-bottom: 2px;
+ }
+
+ &__name {
+ display: flex;
+ flex-flow: row nowrap;
+ font-size: 12px;
+
+ &__label {
+ margin-right: .25rem;
+ }
+ }
+
+ &__close-area {
+ z-index: 2000;
+ }
+
+ &__list {
+ z-index: 2050;
+ position: absolute;
+ height: 220px;
+ width: 100%;
+ border: 1px solid $geyser;
+ border-radius: 4px;
+ background-color: $white;
+ box-shadow: 0 3px 6px 0 rgba(0 ,0 ,0 ,.11);
+ top: 65px;
+ left: 0;
+ box-sizing: content-box;
+ overflow-y: scroll;
+ margin-top: 0;
+ padding: 4px 0;
+
+ .send-v2__asset-dropdown__asset {
+ padding: 8px;
+ }
+ }
+
+ &__input-wrapper {
+ border: 1px solid $alto;
+ border-radius: 4px;
+ height: 100%;
+
+ &--opened {
+ position: relative;
+ z-index: 2050;
+ }
+
+ .send-v2__asset-dropdown__asset {
+ height: 100%;
+ &:hover {
+ background-color: $white;
+ }
+ }
+ }
+
+ &__input {
+ z-index: 1025;
+ position: relative;
+ height: 54px;
+ width: 100%;
+ border: none;
+ border-radius: 4px;
+ background-color: $white;
+ color: $tundora;
+ padding: 10px;
+ font-family: Roboto;
+ font-size: 16px;
+ line-height: 21px;
+ }
+ }
+
&__to-autocomplete {
position: relative;
@@ -657,7 +773,43 @@
}
}
- &__to-autocomplete, &__memo-text-area, &__hex-data {
+ &__to-autocomplete {
+ display: flex;
+ flex-direction: column;
+ z-index: 1025;
+ position: relative;
+ height: 54px;
+ width: 100%;
+ border: 1px solid $alto;
+ border-radius: 4px;
+ background-color: $white;
+ color: $tundora;
+ padding: 0 10px;
+ font-family: Roboto;
+ line-height: 21px;
+
+ &__input {
+ font-size: 16px;
+ height: 100%;
+ border: none;
+ }
+
+ &__resolved {
+ font-size: 12px;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ white-space: nowrap;
+ height: 30px;
+ cursor: pointer;
+
+ + .send-v2__to-autocomplete__qr-code {
+ top: 2px;
+ right: 0;
+ }
+ }
+ }
+
+ &__memo-text-area, &__hex-data {
&__input {
z-index: 1025;
position: relative;
@@ -675,12 +827,47 @@
}
&__amount-max {
- color: $curious-blue;
font-family: Roboto;
font-size: 12px;
- left: 8px;
- border: none;
- cursor: pointer;
+ position: relative;
+ display: inline-block;
+ width: 56px;
+ height: 20px;
+ margin-top: 5px;
+
+ &__button {
+ width: 56px;
+ height: 20px;
+ position: absolute;
+ border: 2px solid #B0D7F2;
+ border-radius: 6px;
+ cursor: pointer;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #2f9ae0;
+
+ &__disabled {
+ color: #B0D7F2;
+ cursor: auto;
+ }
+ }
+
+ input:checked + &__button {
+ background-color: #037DD6;
+ border: 2px solid #037DD6;
+ color: #fff;
+ }
+ }
+
+ &__amount-max input {
+ opacity: 0;
+ width: 0;
+ height: 0;
}
&__gas-fee-display {
@@ -935,7 +1122,7 @@
font-size: 14px;
color: #2f9ae0;
cursor: pointer;
- margin-top: 16px;
+ margin-top: 5px;
}
.sliders-icon-container {
diff --git a/ui/app/css/itcss/generic/index.scss b/ui/app/css/itcss/generic/index.scss
index d8e62c97a..8b282aa1e 100644
--- a/ui/app/css/itcss/generic/index.scss
+++ b/ui/app/css/itcss/generic/index.scss
@@ -18,6 +18,7 @@ body {
height: 100%;
margin: 0;
padding: 0;
+ font-size: 16px;
@media screen and (max-width: $break-small) {
overflow-y: overlay;
diff --git a/ui/app/css/itcss/settings/typography.scss b/ui/app/css/itcss/settings/typography.scss
index 18c444c8a..93107a106 100644
--- a/ui/app/css/itcss/settings/typography.scss
+++ b/ui/app/css/itcss/settings/typography.scss
@@ -403,3 +403,40 @@
font-weight: 400;
font-style: normal;
}
+
+@mixin fontScale($weight: 400, $size: 1rem) {
+ font-weight: $weight;
+ font-size: $size;
+}
+
+@mixin h1($weight: 400, $size: 2.5rem){
+ @include fontScale($weight, $size);
+}
+
+@mixin h2($weight: 400, $size: 2rem){
+ @include fontScale($weight, $size);
+}
+
+@mixin h3($weight: 400, $size: 1.5rem){
+ @include fontScale($weight, $size);
+}
+
+@mixin h4($weight: 400, $size: 1.125rem){
+ @include fontScale($weight, $size);
+}
+
+@mixin h5($weight: 400, $size: 1rem){
+ @include fontScale($weight, $size);
+}
+
+@mixin h6($weight: 400, $size: .875rem){
+ @include fontScale($weight, $size);
+}
+
+@mixin h7($weight: 400, $size: .75rem){
+ @include fontScale($weight, $size);
+}
+
+@mixin paragraph($weight: 400, $size: 1rem){
+ @include fontScale($weight, $size);
+}
diff --git a/ui/app/css/itcss/settings/variables.scss b/ui/app/css/itcss/settings/variables.scss
index 89bd8b96a..f7003b1f4 100644
--- a/ui/app/css/itcss/settings/variables.scss
+++ b/ui/app/css/itcss/settings/variables.scss
@@ -26,7 +26,7 @@ $dusty-gray: #9b9b9b;
$alto: #dedede;
$alabaster: #fafafa;
$silver-chalice: #aeaeae;
-$curious-blue: #2f9ae0;
+$curious-blue: #037DD6;
$concrete: #f3f3f3;
$tundora: #4d4d4d;
$nile-blue: #1b344d;
@@ -93,3 +93,19 @@ $break-large: 576px;
$primary-font-type: Roboto;
+$Blue-000: #eaf6ff;
+$Blue-400: #1098fc;
+$Blue-500: #037DD6;
+$Blue-600: #0260a4;
+
+$Grey-000: #f2f3f4;
+$Grey-500: #6A737D;
+
+$Red-000: #fcf2f3;
+$Red-500: #D73A49;
+$Red-600: #b92534;
+
+$Orange-000: #fef5ef;
+$Orange-500: #F66A0A;
+
+
diff --git a/ui/app/ducks/app/app.js b/ui/app/ducks/app/app.js
index 295507d70..b181092c1 100644
--- a/ui/app/ducks/app/app.js
+++ b/ui/app/ducks/app/app.js
@@ -77,6 +77,8 @@ function reduceApp (state, action) {
ledger: `m/44'/60'/0'/0/0`,
},
lastSelectedProvider: null,
+ networksTabSelectedRpcUrl: '',
+ networksTabIsInAddMode: false,
}, state.appState)
switch (action.type) {
@@ -751,6 +753,16 @@ function reduceApp (state, action) {
lastSelectedProvider: action.value,
})
+ case actions.SET_SELECTED_SETTINGS_RPC_URL:
+ return extend(appState, {
+ networksTabSelectedRpcUrl: action.value,
+ })
+
+ case actions.SET_NETWORKS_TAB_ADD_MODE:
+ return extend(appState, {
+ networksTabIsInAddMode: action.value,
+ })
+
default:
return appState
}
diff --git a/ui/app/ducks/confirm-transaction/confirm-transaction.duck.js b/ui/app/ducks/confirm-transaction/confirm-transaction.duck.js
index 169c9d543..58b0ec8e8 100644
--- a/ui/app/ducks/confirm-transaction/confirm-transaction.duck.js
+++ b/ui/app/ducks/confirm-transaction/confirm-transaction.duck.js
@@ -375,7 +375,7 @@ export function setTransactionToConfirm (transactionId) {
dispatch(updateMethodData(methodData))
try {
- const toSmartContract = await isSmartContractAddress(to)
+ const toSmartContract = await isSmartContractAddress(to || '')
dispatch(updateToSmartContract(toSmartContract))
} catch (error) {
log.error(error)
diff --git a/ui/app/ducks/confirm-transaction/confirm-transaction.duck.test.js b/ui/app/ducks/confirm-transaction/confirm-transaction.duck.test.js
index 483f2f56d..d2e344663 100644
--- a/ui/app/ducks/confirm-transaction/confirm-transaction.duck.test.js
+++ b/ui/app/ducks/confirm-transaction/confirm-transaction.duck.test.js
@@ -494,7 +494,7 @@ describe('Confirm Transaction Duck', () => {
})
})
- describe('Thunk actions', done => {
+ describe('Thunk actions', () => {
beforeEach(() => {
global.eth = {
getCode: sinon.stub().callsFake(
diff --git a/ui/app/ducks/gas/gas-duck.test.js b/ui/app/ducks/gas/gas-duck.test.js
index c0152c74f..b7e83a81c 100644
--- a/ui/app/ducks/gas/gas-duck.test.js
+++ b/ui/app/ducks/gas/gas-duck.test.js
@@ -461,8 +461,8 @@ describe('Gas Duck', () => {
assert.equal(thirdDispatchCallType, SET_PRICE_AND_TIME_ESTIMATES)
assert(priceAndTimeEstimateResult.length < mockPredictTableResponse.length * 3 - 2)
assert(!priceAndTimeEstimateResult.find(d => d.expectedTime > 100))
- assert(!priceAndTimeEstimateResult.find((d, i, a) => a[a + 1] && d.expectedTime > a[a + 1].expectedTime))
- assert(!priceAndTimeEstimateResult.find((d, i, a) => a[a + 1] && d.gasprice > a[a + 1].gasprice))
+ assert(!priceAndTimeEstimateResult.find((d, _, a) => a[a + 1] && d.expectedTime > a[a + 1].expectedTime))
+ assert(!priceAndTimeEstimateResult.find((d, _, a) => a[a + 1] && d.gasprice > a[a + 1].gasprice))
assert.deepEqual(
mockDistpatch.getCall(3).args,
diff --git a/ui/app/ducks/metamask/metamask.js b/ui/app/ducks/metamask/metamask.js
index 47c767d68..3ca487c1f 100644
--- a/ui/app/ducks/metamask/metamask.js
+++ b/ui/app/ducks/metamask/metamask.js
@@ -154,9 +154,26 @@ function reduceMetamask (state, action) {
return newState
case actions.SET_SELECTED_TOKEN:
- return extend(metamaskState, {
+ newState = extend(metamaskState, {
selectedTokenAddress: action.value,
})
+ const newSend = extend(metamaskState.send)
+
+ if (metamaskState.send.editingTransactionId && !action.value) {
+ delete newSend.token
+ const unapprovedTx = newState.unapprovedTxs[newSend.editingTransactionId] || {}
+ const txParams = unapprovedTx.txParams || {}
+ newState.unapprovedTxs = extend(newState.unapprovedTxs, {
+ [newSend.editingTransactionId]: extend(unapprovedTx, {
+ txParams: extend(txParams, { data: '' }),
+ }),
+ })
+ newSend.tokenBalance = null
+ newSend.balance = '0'
+ }
+
+ newState.send = newSend
+ return newState
case actions.SET_ACCOUNT_LABEL:
const account = action.value.account
diff --git a/ui/app/helpers/constants/common.js b/ui/app/helpers/constants/common.js
index 58fae5e5f..a0d6e65b3 100644
--- a/ui/app/helpers/constants/common.js
+++ b/ui/app/helpers/constants/common.js
@@ -10,4 +10,5 @@ export const NETWORK_TYPES = {
MAINNET: 'mainnet',
RINKEBY: 'rinkeby',
ROPSTEN: 'ropsten',
+ GOERLI: 'goerli',
}
diff --git a/ui/app/helpers/constants/routes.js b/ui/app/helpers/constants/routes.js
index df35112d1..d906fc8e6 100644
--- a/ui/app/helpers/constants/routes.js
+++ b/ui/app/helpers/constants/routes.js
@@ -8,6 +8,7 @@ const ADVANCED_ROUTE = '/settings/advanced'
const SECURITY_ROUTE = '/settings/security'
const COMPANY_ROUTE = '/settings/company'
const ABOUT_US_ROUTE = '/settings/about-us'
+const NETWORKS_ROUTE = '/settings/networks'
const REVEAL_SEED_ROUTE = '/seed'
const MOBILE_SYNC_ROUTE = '/mobile-sync'
const CONFIRM_SEED_ROUTE = '/confirm-seed'
@@ -86,4 +87,5 @@ module.exports = {
COMPANY_ROUTE,
GENERAL_ROUTE,
ABOUT_US_ROUTE,
+ NETWORKS_ROUTE,
}
diff --git a/ui/app/helpers/higher-order-components/i18n-provider.js b/ui/app/helpers/higher-order-components/i18n-provider.js
index 0e34e17e0..5a6650147 100644
--- a/ui/app/helpers/higher-order-components/i18n-provider.js
+++ b/ui/app/helpers/higher-order-components/i18n-provider.js
@@ -15,11 +15,21 @@ class I18nProvider extends Component {
const { localeMessages } = this.props
const { current, en } = localeMessages
return {
+ /**
+ * Returns a localized message for the given key
+ * @param {string} key The message key
+ * @param {string[]} args A list of message substitution replacements
+ * @return {string|undefined|null} The localized message if available
+ */
t (key, ...args) {
+ if (key === undefined || key === null) {
+ return key
+ }
+
return t(current, key, ...args) || t(en, key, ...args) || `[${key}]`
},
tOrDefault: this.tOrDefault,
- tOrKey (key, ...args) {
+ tOrKey: (key, ...args) => {
return this.tOrDefault(key, key, ...args)
},
}
diff --git a/ui/app/helpers/higher-order-components/metametrics/metametrics.provider.js b/ui/app/helpers/higher-order-components/metametrics/metametrics.provider.js
index 6086e03fb..6281ddcc6 100644
--- a/ui/app/helpers/higher-order-components/metametrics/metametrics.provider.js
+++ b/ui/app/helpers/higher-order-components/metametrics/metametrics.provider.js
@@ -42,7 +42,7 @@ class MetaMetricsProvider extends Component {
currentPath: window.location.href,
}
- props.history.listen(locationObj => {
+ props.history.listen(() => {
this.setState({
previousPath: this.state.currentPath,
currentPath: window.location.href,
diff --git a/ui/app/helpers/higher-order-components/with-modal-props/tests/with-modal-props.test.js b/ui/app/helpers/higher-order-components/with-modal-props/tests/with-modal-props.test.js
index 654e7062a..81a3512d1 100644
--- a/ui/app/helpers/higher-order-components/with-modal-props/tests/with-modal-props.test.js
+++ b/ui/app/helpers/higher-order-components/with-modal-props/tests/with-modal-props.test.js
@@ -21,7 +21,7 @@ const mockState = {
describe('withModalProps', () => {
it('should return a component wrapped with modal state props', () => {
- const TestComponent = props => (
+ const TestComponent = () => (
<div className="test">Testing</div>
)
const WrappedComponent = withModalProps(TestComponent)
diff --git a/ui/app/helpers/utils/conversion-util.js b/ui/app/helpers/utils/conversion-util.js
index 8cc531773..affddade7 100644
--- a/ui/app/helpers/utils/conversion-util.js
+++ b/ui/app/helpers/utils/conversion-util.js
@@ -42,7 +42,7 @@ const convert = R.invoker(1, 'times')
const round = R.invoker(2, 'round')(R.__, BigNumber.ROUND_HALF_DOWN)
const roundDown = R.invoker(2, 'round')(R.__, BigNumber.ROUND_DOWN)
const invertConversionRate = conversionRate => () => new BigNumber(1.0).div(conversionRate)
-const decToBigNumberViaString = n => R.pipe(String, toBigNumber['dec'])
+const decToBigNumberViaString = () => R.pipe(String, toBigNumber['dec'])
// Setter Maps
const toBigNumber = {
diff --git a/ui/app/helpers/utils/conversions.util.js b/ui/app/helpers/utils/conversions.util.js
index b4ec50626..5e1c21ff7 100644
--- a/ui/app/helpers/utils/conversions.util.js
+++ b/ui/app/helpers/utils/conversions.util.js
@@ -1,6 +1,6 @@
import ethUtil from 'ethereumjs-util'
import { ETH, GWEI, WEI } from '../constants/common'
-import { conversionUtil, addCurrencies } from './conversion-util'
+import { conversionUtil, addCurrencies, subtractCurrencies } from './conversion-util'
export function bnToHex (inputBn) {
return ethUtil.addHexPrefix(inputBn.toString(16))
@@ -92,6 +92,15 @@ export function addHexWEIsToDec (aHexWEI, bHexWEI) {
})
}
+export function subtractHexWEIsToDec (aHexWEI, bHexWEI) {
+ return subtractCurrencies(aHexWEI, bHexWEI, {
+ aBase: 16,
+ bBase: 16,
+ fromDenomination: 'WEI',
+ numberOfDecimals: 6,
+ })
+}
+
export function decEthToConvertedCurrency (ethTotal, convertedCurrency, conversionRate) {
return conversionUtil(ethTotal, {
fromNumericBase: 'dec',
diff --git a/ui/app/helpers/utils/metametrics.util.js b/ui/app/helpers/utils/metametrics.util.js
index 01984bd5e..50270c6a8 100644
--- a/ui/app/helpers/utils/metametrics.util.js
+++ b/ui/app/helpers/utils/metametrics.util.js
@@ -12,6 +12,8 @@ const METAMETRICS_TRACKING_URL = inDevelopment
? 'http://www.metamask.io/metametrics'
: 'http://www.metamask.io/metametrics-prod'
+/** ***************Custom variables*************** **/
+// Custon variable declarations
const METAMETRICS_CUSTOM_GAS_LIMIT_CHANGE = 'gasLimitChange'
const METAMETRICS_CUSTOM_GAS_PRICE_CHANGE = 'gasPriceChange'
const METAMETRICS_CUSTOM_FUNCTION_TYPE = 'functionType'
@@ -23,13 +25,8 @@ const METAMETRICS_CUSTOM_ERROR_FIELD = 'errorField'
const METAMETRICS_CUSTOM_ERROR_MESSAGE = 'errorMessage'
const METAMETRICS_CUSTOM_RPC_NETWORK_ID = 'networkId'
const METAMETRICS_CUSTOM_RPC_CHAIN_ID = 'chainId'
-
-const METAMETRICS_CUSTOM_NETWORK = 'network'
-const METAMETRICS_CUSTOM_ENVIRONMENT_TYPE = 'environmentType'
-const METAMETRICS_CUSTOM_ACTIVE_CURRENCY = 'activeCurrency'
-const METAMETRICS_CUSTOM_ACCOUNT_TYPE = 'accountType'
-const METAMETRICS_CUSTOM_NUMBER_OF_TOKENS = 'numberOfTokens'
-const METAMETRICS_CUSTOM_NUMBER_OF_ACCOUNTS = 'numberOfAccounts'
+const METAMETRICS_CUSTOM_GAS_CHANGED = 'gasChanged'
+const METAMETRICS_CUSTOM_ASSET_SELECTED = 'assetSelected'
const customVariableNameIdMap = {
[METAMETRICS_CUSTOM_FUNCTION_TYPE]: 1,
@@ -37,13 +34,28 @@ const customVariableNameIdMap = {
[METAMETRICS_CUSTOM_CONFIRM_SCREEN_ORIGIN]: 3,
[METAMETRICS_CUSTOM_GAS_LIMIT_CHANGE]: 4,
[METAMETRICS_CUSTOM_GAS_PRICE_CHANGE]: 5,
+
[METAMETRICS_CUSTOM_FROM_NETWORK]: 1,
[METAMETRICS_CUSTOM_TO_NETWORK]: 2,
+
[METAMETRICS_CUSTOM_RPC_NETWORK_ID]: 1,
[METAMETRICS_CUSTOM_RPC_CHAIN_ID]: 2,
- [METAMETRICS_CUSTOM_ERROR_FIELD]: 1,
- [METAMETRICS_CUSTOM_ERROR_MESSAGE]: 2,
+
+ [METAMETRICS_CUSTOM_ERROR_FIELD]: 3,
+ [METAMETRICS_CUSTOM_ERROR_MESSAGE]: 4,
+
+ [METAMETRICS_CUSTOM_GAS_CHANGED]: 1,
+ [METAMETRICS_CUSTOM_ASSET_SELECTED]: 2,
}
+/** ********************************************************** **/
+
+const METAMETRICS_CUSTOM_NETWORK = 'network'
+const METAMETRICS_CUSTOM_ENVIRONMENT_TYPE = 'environmentType'
+const METAMETRICS_CUSTOM_ACTIVE_CURRENCY = 'activeCurrency'
+const METAMETRICS_CUSTOM_ACCOUNT_TYPE = 'accountType'
+const METAMETRICS_CUSTOM_NUMBER_OF_TOKENS = 'numberOfTokens'
+const METAMETRICS_CUSTOM_NUMBER_OF_ACCOUNTS = 'numberOfAccounts'
+
const customDimensionsNameIdMap = {
[METAMETRICS_CUSTOM_NETWORK]: 5,
@@ -59,6 +71,7 @@ function composeUrlRefParamAddition (previousPath, confirmTransactionOrigin) {
return `&urlref=${externalOrigin ? 'EXTERNAL' : encodeURIComponent(previousPath.replace(/chrome-extension:\/\/\w+/, METAMETRICS_TRACKING_URL))}`
}
+// composes query params of the form &dimension[0-999]=[value]
function composeCustomDimensionParamAddition (customDimensions) {
const customDimensionParamStrings = Object.keys(customDimensions).reduce((acc, name) => {
return [...acc, `dimension${customDimensionsNameIdMap[name]}=${customDimensions[name]}`]
@@ -66,6 +79,8 @@ function composeCustomDimensionParamAddition (customDimensions) {
return `&${customDimensionParamStrings.join('&')}`
}
+// composes query params in form: &cvar={[id]:[[name],[value]]}
+// Example: &cvar={"1":["OS","iphone 5.0"],"2":["Matomo Mobile Version","1.6.2"],"3":["Locale","en::en"],"4":["Num Accounts","2"]}
function composeCustomVarParamAddition (customVariables) {
const customVariableIdValuePairs = Object.keys(customVariables).reduce((acc, name) => {
return {
@@ -82,7 +97,29 @@ function composeParamAddition (paramValue, paramName) {
: `&${paramName}=${paramValue}`
}
-function composeUrl (config, permissionPreferences = {}) {
+/**
+ * @name composeUrl
+ * @param {Object} config - configuration object for composing the metametrics url
+ * @property {object} config.eventOpts Object containing event category, action and name descriptors
+ * @property {object} config.customVariables Object containing custom properties with values relevant to a specific event
+ * @property {object} config.pageOpts Objects containing information about a page/route the event is dispatched from
+ * @property {number} config.network The selected network of the user when the event occurs
+ * @property {string} config.environmentType The "environment" the user is using the app from: 'popup', 'notification' or 'fullscreen'
+ * @property {string} config.activeCurrency The current the user has select as their primary currency at the time of the event
+ * @property {string} config.accountType The account type being used at the time of the event: 'hardware', 'imported' or 'default'
+ * @property {number} config.numberOfTokens The number of tokens that the user has added at the time of the event
+ * @property {number} config.numberOfAccounts The number of accounts the user has added at the time of the event
+ * @property {string} config.previousPath The location path the user was on prior to the path they are on at the time of the event
+ * @property {string} config.currentPath The location path the user is on at the time of the event
+ * @property {string} config.metaMetricsId A random id assigned to a user at the time of opting in to metametrics. A hexadecimal number
+ * @property {string} config.confirmTransactionOrigin The origin on a transaction
+ * @property {string} config.url The url to track an event at. Overrides `currentPath`
+ * @property {boolean} config.excludeMetaMetricsId Whether or not the tracked event data should be associated with a metametrics id
+ * @property {boolean} config.isNewVisit Whether or not the event should be tracked as a new visit/user sessions
+ * @returns {String} Returns a url to be passed to fetch to make the appropriate request to matomo.
+ * Example: https://chromeextensionmm.innocraft.cloud/piwik.php?idsite=1&rec=1&apiv=1&e_c=Navigation&e_a=Home&e_n=Clicked%20Send:%20Eth&urlref=http%3A%2F%2Fwww.metamask.io%2Fmetametrics%2Fhome.html%23send&dimension5=3&dimension6=fullscreen&dimension7=ETH&dimension8=default&dimension9=0&dimension10=3&url=http%3A%2F%2Fwww.metamask.io%2Fmetametrics%2Fhome.html%23&_id=49c10aff19795e9a&rand=7906028754863992&pv_id=53acad&uid=49c1
+ */
+function composeUrl (config) {
const {
eventOpts = {},
customVariables = '',
@@ -122,10 +159,10 @@ function composeUrl (config, permissionPreferences = {}) {
numberOfTokens: customVariables && customVariables.numberOfTokens || numberOfTokens,
numberOfAccounts: customVariables && customVariables.numberOfAccounts || numberOfAccounts,
}) : ''
- const url = configUrl || `&url=${encodeURIComponent(currentPath.replace(/chrome-extension:\/\/\w+/, METAMETRICS_TRACKING_URL))}`
+ const url = configUrl || currentPath ? `&url=${encodeURIComponent(currentPath.replace(/chrome-extension:\/\/\w+/, METAMETRICS_TRACKING_URL))}` : ''
const _id = metaMetricsId && !excludeMetaMetricsId ? `&_id=${metaMetricsId.slice(2, 18)}` : ''
const rand = `&rand=${String(Math.random()).slice(2)}`
- const pv_id = `&pv_id=${ethUtil.bufferToHex(ethUtil.sha3(url || currentPath.match(/chrome-extension:\/\/\w+\/(.+)/)[0])).slice(2, 8)}`
+ const pv_id = (url || currentPath) && `&pv_id=${ethUtil.bufferToHex(ethUtil.sha3(url || currentPath.match(/chrome-extension:\/\/\w+\/(.+)/)[0])).slice(2, 8)}` || ''
const uid = metaMetricsId && !excludeMetaMetricsId
? `&uid=${metaMetricsId.slice(2, 18)}`
: excludeMetaMetricsId
diff --git a/ui/app/helpers/utils/transactions.util.js b/ui/app/helpers/utils/transactions.util.js
index cb6c9536c..99ccc3478 100644
--- a/ui/app/helpers/utils/transactions.util.js
+++ b/ui/app/helpers/utils/transactions.util.js
@@ -6,6 +6,8 @@ import {
TRANSACTION_TYPE_CANCEL,
TRANSACTION_STATUS_CONFIRMED,
} from '../../../../app/scripts/controllers/transactions/enums'
+import prefixForNetwork from '../../../lib/etherscan-prefix-for-network'
+
import {
TOKEN_METHOD_TRANSFER,
@@ -188,3 +190,17 @@ export function getStatusKey (transaction) {
return transaction.status
}
+
+/**
+ * Returns an external block explorer URL at which a transaction can be viewed.
+ * @param {number} networkId
+ * @param {string} hash
+ * @param {Object} rpcPrefs
+ */
+export function getBlockExplorerUrlForTx (networkId, hash, rpcPrefs = {}) {
+ if (rpcPrefs.blockExplorerUrl) {
+ return `${rpcPrefs.blockExplorerUrl}/tx/${hash}`
+ }
+ const prefix = prefixForNetwork(networkId)
+ return `https://${prefix}etherscan.io/tx/${hash}`
+}
diff --git a/ui/app/helpers/utils/util.js b/ui/app/helpers/utils/util.js
index c50d7cbe5..94fa9ad42 100644
--- a/ui/app/helpers/utils/util.js
+++ b/ui/app/helpers/utils/util.js
@@ -92,7 +92,7 @@ function miniAddressSummary (address) {
return checked ? checked.slice(0, 4) + '...' + checked.slice(-4) : '...'
}
-function isValidAddress (address, network) {
+function isValidAddress (address) {
var prefixed = ethUtil.addHexPrefix(address)
if (address === '0x0000000000000000000000000000000000000000') return false
return (isAllOneCase(prefixed) && ethUtil.isValidAddress(prefixed)) || ethUtil.isValidChecksumAddress(prefixed)
@@ -268,7 +268,7 @@ function bnMultiplyByFraction (targetBN, numerator, denominator) {
return targetBN.mul(numBN).div(denomBN)
}
-function getTxFeeBn (gas, gasPrice = MIN_GAS_PRICE_BN.toString(16), blockGasLimit) {
+function getTxFeeBn (gas, gasPrice = MIN_GAS_PRICE_BN.toString(16)) {
const gasBn = hexToBn(gas)
const gasPriceBn = hexToBn(gasPrice)
const txFeeBn = gasBn.mul(gasPriceBn)
@@ -297,7 +297,7 @@ function exportAsFile (filename, data, type = 'text/csv') {
}
function allNull (obj) {
- return Object.entries(obj).every(([key, value]) => value === null)
+ return Object.entries(obj).every(([_, value]) => value === null)
}
function getTokenAddressFromTokenObject (token) {
@@ -308,11 +308,10 @@ function getTokenAddressFromTokenObject (token) {
* Safely checksumms a potentially-null address
*
* @param {String} [address] - address to checksum
- * @param {String} [network] - network id
* @returns {String} - checksummed address
*
*/
-function checksumAddress (address, network) {
+function checksumAddress (address) {
const checksummed = address ? ethUtil.toChecksumAddress(address) : ''
return checksummed
}
diff --git a/ui/app/pages/add-token/token-list/token-list-placeholder/index.scss b/ui/app/pages/add-token/token-list/token-list-placeholder/index.scss
index cc495dfb0..a363c77c5 100644
--- a/ui/app/pages/add-token/token-list/token-list-placeholder/index.scss
+++ b/ui/app/pages/add-token/token-list/token-list-placeholder/index.scss
@@ -18,6 +18,7 @@
}
&__link {
- color: $curious-blue;
+ @extend %link;
+ margin-top: .5rem;
}
}
diff --git a/ui/app/pages/confirm-add-suggested-token/confirm-add-suggested-token.component.js b/ui/app/pages/confirm-add-suggested-token/confirm-add-suggested-token.component.js
index 7edb8f541..9a118a815 100644
--- a/ui/app/pages/confirm-add-suggested-token/confirm-add-suggested-token.component.js
+++ b/ui/app/pages/confirm-add-suggested-token/confirm-add-suggested-token.component.js
@@ -103,7 +103,7 @@ export default class ConfirmAddSuggestedToken extends Component {
{ this.context.t('cancel') }
</Button>
<Button
- type="primary"
+ type="secondary"
large
className="page-container__footer-button"
onClick={() => {
diff --git a/ui/app/pages/confirm-add-suggested-token/confirm-add-suggested-token.container.js b/ui/app/pages/confirm-add-suggested-token/confirm-add-suggested-token.container.js
index a90fe148f..cc73b2ea7 100644
--- a/ui/app/pages/confirm-add-suggested-token/confirm-add-suggested-token.container.js
+++ b/ui/app/pages/confirm-add-suggested-token/confirm-add-suggested-token.container.js
@@ -18,7 +18,7 @@ const mapStateToProps = ({ metamask }) => {
const mapDispatchToProps = dispatch => {
return {
- addToken: ({address, symbol, decimals, image}) => dispatch(addToken(address, symbol, decimals, image)),
+ addToken: ({address, symbol, decimals, image}) => dispatch(addToken(address, symbol, Number(decimals), image)),
removeSuggestedTokens: () => dispatch(removeSuggestedTokens()),
}
}
diff --git a/ui/app/pages/confirm-add-token/confirm-add-token.component.js b/ui/app/pages/confirm-add-token/confirm-add-token.component.js
index c0ec624ac..f0a19e8d9 100644
--- a/ui/app/pages/confirm-add-token/confirm-add-token.component.js
+++ b/ui/app/pages/confirm-add-token/confirm-add-token.component.js
@@ -96,7 +96,7 @@ export default class ConfirmAddToken extends Component {
{ this.context.t('back') }
</Button>
<Button
- type="primary"
+ type="secondary"
large
className="page-container__footer-button"
onClick={() => {
diff --git a/ui/app/pages/confirm-deploy-contract/confirm-deploy-contract.component.js b/ui/app/pages/confirm-deploy-contract/confirm-deploy-contract.component.js
index 9bc0daab9..c90ccc917 100644
--- a/ui/app/pages/confirm-deploy-contract/confirm-deploy-contract.component.js
+++ b/ui/app/pages/confirm-deploy-contract/confirm-deploy-contract.component.js
@@ -56,7 +56,7 @@ export default class ConfirmDeployContract extends Component {
render () {
return (
<ConfirmTransactionBase
- action={this.context.t('contractDeployment')}
+ actionKey={'contractDeployment'}
dataComponent={this.renderData()}
/>
)
diff --git a/ui/app/pages/confirm-send-ether/confirm-send-ether.component.js b/ui/app/pages/confirm-send-ether/confirm-send-ether.component.js
index 8daad675e..68280f624 100644
--- a/ui/app/pages/confirm-send-ether/confirm-send-ether.component.js
+++ b/ui/app/pages/confirm-send-ether/confirm-send-ether.component.js
@@ -30,7 +30,7 @@ export default class ConfirmSendEther extends Component {
return (
<ConfirmTransactionBase
- action={this.context.t('confirm')}
+ actionKey={'confirm'}
hideData={hideData}
onEdit={confirmTransactionData => this.handleEdit(confirmTransactionData)}
/>
diff --git a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js
index 9e749322f..c6a05cf0f 100644
--- a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js
+++ b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.component.js
@@ -4,11 +4,12 @@ import PropTypes from 'prop-types'
import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../../app/scripts/lib/enums'
import { getEnvironmentType } from '../../../../app/scripts/lib/util'
import ConfirmPageContainer, { ConfirmDetailRow } from '../../components/app/confirm-page-container'
-import { isBalanceSufficient } from '../../components/app/send/send.utils'
+import { isBalanceSufficient } from '../send/send.utils'
import { DEFAULT_ROUTE, CONFIRM_TRANSACTION_ROUTE } from '../../helpers/constants/routes'
import {
INSUFFICIENT_FUNDS_ERROR_KEY,
TRANSACTION_ERROR_KEY,
+ GAS_LIMIT_TOO_LOW_ERROR_KEY,
} from '../../helpers/constants/error-keys'
import { CONFIRMED_STATUS, DROPPED_STATUS } from '../../helpers/constants/transactions'
import UserPreferencedCurrencyDisplay from '../../components/app/user-preferenced-currency-display'
@@ -18,6 +19,7 @@ import AdvancedGasInputs from '../../components/app/gas-customization/advanced-g
export default class ConfirmTransactionBase extends Component {
static contextTypes = {
t: PropTypes.func,
+ tOrKey: PropTypes.func.isRequired,
metricsEvent: PropTypes.func,
}
@@ -64,7 +66,7 @@ export default class ConfirmTransactionBase extends Component {
updateGasAndCalculate: PropTypes.func,
customGas: PropTypes.object,
// Component props
- action: PropTypes.string,
+ actionKey: PropTypes.string,
contentComponent: PropTypes.node,
dataComponent: PropTypes.node,
detailsComponent: PropTypes.node,
@@ -99,15 +101,18 @@ export default class ConfirmTransactionBase extends Component {
submitError: null,
}
- componentDidUpdate () {
+ componentDidUpdate (prevProps) {
const {
transactionStatus,
showTransactionConfirmedModal,
history,
clearConfirmTransaction,
} = this.props
+ const { transactionStatus: prevTxStatus } = prevProps
+ const statusUpdated = transactionStatus !== prevTxStatus
+ const txDroppedOrConfirmed = transactionStatus === DROPPED_STATUS || transactionStatus === CONFIRMED_STATUS
- if (transactionStatus === DROPPED_STATUS || transactionStatus === CONFIRMED_STATUS) {
+ if (statusUpdated && txDroppedOrConfirmed) {
showTransactionConfirmedModal({
onSubmit: () => {
clearConfirmTransaction()
@@ -130,6 +135,7 @@ export default class ConfirmTransactionBase extends Component {
value: amount,
} = {},
} = {},
+ customGas,
} = this.props
const insufficientBalance = balance && !isBalanceSufficient({
@@ -146,6 +152,13 @@ export default class ConfirmTransactionBase extends Component {
}
}
+ if (customGas.gasLimit < 21000) {
+ return {
+ valid: false,
+ errorKey: GAS_LIMIT_TOO_LOW_ERROR_KEY,
+ }
+ }
+
if (simulationFails) {
return {
valid: true,
@@ -159,7 +172,7 @@ export default class ConfirmTransactionBase extends Component {
}
handleEditGas () {
- const { onEditGas, showCustomizeGasModal, action, txData: { origin }, methodData = {} } = this.props
+ const { onEditGas, showCustomizeGasModal, actionKey, txData: { origin }, methodData = {} } = this.props
this.context.metricsEvent({
eventOpts: {
@@ -169,7 +182,7 @@ export default class ConfirmTransactionBase extends Component {
},
customVariables: {
recipientKnown: null,
- functionType: action || getMethodName(methodData.name) || this.context.t('contractInteraction'),
+ functionType: actionKey || getMethodName(methodData.name) || 'contractInteraction',
origin,
},
})
@@ -292,7 +305,7 @@ export default class ConfirmTransactionBase extends Component {
}
handleEdit () {
- const { txData, tokenData, tokenProps, onEdit, action, txData: { origin }, methodData = {} } = this.props
+ const { txData, tokenData, tokenProps, onEdit, actionKey, txData: { origin }, methodData = {} } = this.props
this.context.metricsEvent({
eventOpts: {
@@ -302,7 +315,7 @@ export default class ConfirmTransactionBase extends Component {
},
customVariables: {
recipientKnown: null,
- functionType: action || getMethodName(methodData.name) || this.context.t('contractInteraction'),
+ functionType: actionKey || getMethodName(methodData.name) || 'contractInteraction',
origin,
},
})
@@ -331,7 +344,7 @@ export default class ConfirmTransactionBase extends Component {
handleCancel () {
const { metricsEvent } = this.context
- const { onCancel, txData, cancelTransaction, history, clearConfirmTransaction, action, txData: { origin }, methodData = {} } = this.props
+ const { onCancel, txData, cancelTransaction, history, clearConfirmTransaction, actionKey, txData: { origin }, methodData = {} } = this.props
if (onCancel) {
metricsEvent({
@@ -342,7 +355,7 @@ export default class ConfirmTransactionBase extends Component {
},
customVariables: {
recipientKnown: null,
- functionType: action || getMethodName(methodData.name) || this.context.t('contractInteraction'),
+ functionType: actionKey || getMethodName(methodData.name) || 'contractInteraction',
origin,
},
})
@@ -358,7 +371,7 @@ export default class ConfirmTransactionBase extends Component {
handleSubmit () {
const { metricsEvent } = this.context
- const { txData: { origin }, sendTransaction, clearConfirmTransaction, txData, history, onSubmit, action, metaMetricsSendCount = 0, setMetaMetricsSendCount, methodData = {} } = this.props
+ const { txData: { origin }, sendTransaction, clearConfirmTransaction, txData, history, onSubmit, actionKey, metaMetricsSendCount = 0, setMetaMetricsSendCount, methodData = {} } = this.props
const { submitting } = this.state
if (submitting) {
@@ -377,7 +390,7 @@ export default class ConfirmTransactionBase extends Component {
},
customVariables: {
recipientKnown: null,
- functionType: action || getMethodName(methodData.name) || this.context.t('contractInteraction'),
+ functionType: actionKey || getMethodName(methodData.name) || 'contractInteraction',
origin,
},
})
@@ -517,7 +530,7 @@ export default class ConfirmTransactionBase extends Component {
valid: propsValid = true,
errorMessage,
errorKey: propsErrorKey,
- action,
+ actionKey,
title,
subtitle,
hideSubtitle,
@@ -543,7 +556,8 @@ export default class ConfirmTransactionBase extends Component {
toName={toName}
toAddress={toAddress}
showEdit={onEdit && !isTxReprice}
- action={action || getMethodName(name) || this.context.t('contractInteraction')}
+ // In the event that the key is falsy (and inherently invalid), use a fallback string
+ action={this.context.tOrKey(actionKey) || getMethodName(name) || this.context.t('contractInteraction')}
title={title}
titleComponent={this.renderTitleComponent()}
subtitle={subtitle}
diff --git a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.container.js b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.container.js
index 83543f1a4..2b087f5cc 100644
--- a/ui/app/pages/confirm-transaction-base/confirm-transaction-base.container.js
+++ b/ui/app/pages/confirm-transaction-base/confirm-transaction-base.container.js
@@ -14,9 +14,9 @@ import {
GAS_LIMIT_TOO_LOW_ERROR_KEY,
} from '../../helpers/constants/error-keys'
import { getHexGasTotal } from '../../helpers/utils/confirm-tx.util'
-import { isBalanceSufficient, calcGasTotal } from '../../components/app/send/send.utils'
+import { isBalanceSufficient, calcGasTotal } from '../send/send.utils'
import { conversionGreaterThan } from '../../helpers/utils/conversion-util'
-import { MIN_GAS_LIMIT_DEC } from '../../components/app/send/send.constants'
+import { MIN_GAS_LIMIT_DEC } from '../send/send.constants'
import { checksumAddress, addressSlicer, valuesFor } from '../../helpers/utils/util'
import {getMetaMaskAccounts, getAdvancedInlineGasShown, preferencesSelector, getIsMainnet} from '../../selectors/selectors'
diff --git a/ui/app/pages/create-account/connect-hardware/account-list.js b/ui/app/pages/create-account/connect-hardware/account-list.js
index 617fb8833..247c27a5d 100644
--- a/ui/app/pages/create-account/connect-hardware/account-list.js
+++ b/ui/app/pages/create-account/connect-hardware/account-list.js
@@ -6,10 +6,6 @@ const Select = require('react-select').default
import Button from '../../../components/ui/button'
class AccountList extends Component {
- constructor (props, context) {
- super(props)
- }
-
getHdPaths () {
return [
{
@@ -152,7 +148,7 @@ class AccountList extends Component {
}, [this.context.t('cancel')]),
h(Button, {
- type: 'confirm',
+ type: 'primary',
large: true,
className: 'new-account-connect-form__button unlock',
disabled,
diff --git a/ui/app/pages/create-account/connect-hardware/connect-screen.js b/ui/app/pages/create-account/connect-hardware/connect-screen.js
index 7e9dee970..a3b8ad246 100644
--- a/ui/app/pages/create-account/connect-hardware/connect-screen.js
+++ b/ui/app/pages/create-account/connect-hardware/connect-screen.js
@@ -4,7 +4,7 @@ const h = require('react-hyperscript')
import Button from '../../../components/ui/button'
class ConnectScreen extends Component {
- constructor (props, context) {
+ constructor (props) {
super(props)
this.state = {
selectedDevice: null,
@@ -46,7 +46,7 @@ class ConnectScreen extends Component {
this.renderConnectToTrezorButton(),
]),
h(Button, {
- type: 'confirm',
+ type: 'primary',
large: true,
className: 'hw-connect__connect-btn',
onClick: this.connect,
@@ -103,7 +103,7 @@ class ConnectScreen extends Component {
}
- scrollToTutorial = (e) => {
+ scrollToTutorial = () => {
if (this.referenceNode) this.referenceNode.scrollIntoView({behavior: 'smooth'})
}
diff --git a/ui/app/pages/create-account/connect-hardware/index.js b/ui/app/pages/create-account/connect-hardware/index.js
index 1398fa680..80a160205 100644
--- a/ui/app/pages/create-account/connect-hardware/index.js
+++ b/ui/app/pages/create-account/connect-hardware/index.js
@@ -8,11 +8,9 @@ const ConnectScreen = require('./connect-screen')
const AccountList = require('./account-list')
const { DEFAULT_ROUTE } = require('../../../helpers/constants/routes')
const { formatBalance } = require('../../../helpers/utils/util')
-const { getPlatform } = require('../../../../../app/scripts/lib/util')
-const { PLATFORM_FIREFOX } = require('../../../../../app/scripts/lib/enums')
class ConnectHardwareForm extends Component {
- constructor (props, context) {
+ constructor (props) {
super(props)
this.state = {
error: null,
@@ -51,12 +49,6 @@ class ConnectHardwareForm extends Component {
}
connectToHardwareWallet = (device) => {
- // Ledger hardware wallets are not supported on firefox
- if (getPlatform() === PLATFORM_FIREFOX && device === 'ledger') {
- this.setState({ browserSupported: false, error: null})
- return null
- }
-
if (this.state.accounts.length) {
return null
}
@@ -101,7 +93,7 @@ class ConnectHardwareForm extends Component {
const newState = { unlocked: true, device, error: null }
// Default to the first account
if (this.state.selectedAccount === null) {
- accounts.forEach((a, i) => {
+ accounts.forEach((a) => {
if (a.address.toLowerCase() === this.props.address) {
newState.selectedAccount = a.index.toString()
}
diff --git a/ui/app/pages/create-account/import-account/json.js b/ui/app/pages/create-account/import-account/json.js
index 17bef763c..ad430ba58 100644
--- a/ui/app/pages/create-account/import-account/json.js
+++ b/ui/app/pages/create-account/import-account/json.js
@@ -61,7 +61,7 @@ class JsonImportSubview extends Component {
}, [this.context.t('cancel')]),
h(Button, {
- type: 'primary',
+ type: 'secondary',
large: true,
className: 'new-account-create-form__button',
onClick: () => this.createNewKeychain(),
diff --git a/ui/app/pages/create-account/import-account/private-key.js b/ui/app/pages/create-account/import-account/private-key.js
index 450614e87..0cdf25ce9 100644
--- a/ui/app/pages/create-account/import-account/private-key.js
+++ b/ui/app/pages/create-account/import-account/private-key.js
@@ -75,7 +75,7 @@ PrivateKeyImportView.prototype.render = function () {
}, [this.context.t('cancel')]),
h(Button, {
- type: 'primary',
+ type: 'secondary',
large: true,
className: 'new-account-create-form__button',
onClick: () => this.createNewKeychain(),
diff --git a/ui/app/pages/create-account/import-account/seed.js b/ui/app/pages/create-account/import-account/seed.js
index d98909baa..73332f926 100644
--- a/ui/app/pages/create-account/import-account/seed.js
+++ b/ui/app/pages/create-account/import-account/seed.js
@@ -11,7 +11,7 @@ SeedImportSubview.contextTypes = {
module.exports = connect(mapStateToProps)(SeedImportSubview)
-function mapStateToProps (state) {
+function mapStateToProps () {
return {}
}
diff --git a/ui/app/pages/create-account/new-account.js b/ui/app/pages/create-account/new-account.js
index 316fbe6f1..d19e6bc38 100644
--- a/ui/app/pages/create-account/new-account.js
+++ b/ui/app/pages/create-account/new-account.js
@@ -47,7 +47,7 @@ class NewAccountCreateForm extends Component {
}, [this.context.t('cancel')]),
h(Button, {
- type: 'primary',
+ type: 'secondary',
large: true,
className: 'new-account-create-form__button',
onClick: () => {
diff --git a/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js b/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js
index 433dad6e2..5092d277e 100644
--- a/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js
+++ b/ui/app/pages/first-time-flow/create-password/import-with-seed-phrase/import-with-seed-phrase.component.js
@@ -36,6 +36,20 @@ export default class ImportWithSeedPhrase extends PureComponent {
.join(' ')
}
+ componentWillMount () {
+ window.onbeforeunload = () => this.context.metricsEvent({
+ eventOpts: {
+ category: 'Onboarding',
+ action: 'Import Seed Phrase',
+ name: 'Close window on import screen',
+ },
+ customVariables: {
+ errorLabel: 'Seed Phrase Error',
+ errorMessage: this.state.seedPhraseError,
+ },
+ })
+ }
+
handleSeedPhraseChange (seedPhrase) {
let seedPhraseError = ''
@@ -172,6 +186,10 @@ export default class ImportWithSeedPhrase extends PureComponent {
action: 'Import Seed Phrase',
name: 'Go Back from Onboarding Import',
},
+ customVariables: {
+ errorLabel: 'Seed Phrase Error',
+ errorMessage: seedPhraseError,
+ },
})
this.props.history.push(INITIALIZE_SELECT_ACTION_ROUTE)
}}
@@ -243,7 +261,7 @@ export default class ImportWithSeedPhrase extends PureComponent {
</span>
</div>
<Button
- type="confirm"
+ type="primary"
className="first-time-flow__button"
disabled={!this.isValid() || !termsChecked}
onClick={this.handleImport}
diff --git a/ui/app/pages/first-time-flow/create-password/new-account/new-account.component.js b/ui/app/pages/first-time-flow/create-password/new-account/new-account.component.js
index c040cff88..de073af2f 100644
--- a/ui/app/pages/first-time-flow/create-password/new-account/new-account.component.js
+++ b/ui/app/pages/first-time-flow/create-password/new-account/new-account.component.js
@@ -211,7 +211,7 @@ export default class NewAccount extends PureComponent {
</span>
</div>
<Button
- type="confirm"
+ type="primary"
className="first-time-flow__button"
disabled={!this.isValid() || !termsChecked}
onClick={this.handleCreate}
diff --git a/ui/app/pages/first-time-flow/create-password/unique-image/unique-image.component.js b/ui/app/pages/first-time-flow/create-password/unique-image/unique-image.component.js
index 3434d117a..590cf0303 100644
--- a/ui/app/pages/first-time-flow/create-password/unique-image/unique-image.component.js
+++ b/ui/app/pages/first-time-flow/create-password/unique-image/unique-image.component.js
@@ -34,7 +34,7 @@ export default class UniqueImageScreen extends PureComponent {
{ t('protectYourKeysMessage2') }
</div>
<Button
- type="confirm"
+ type="primary"
className="first-time-flow__button"
onClick={() => {
this.context.metricsEvent({
diff --git a/ui/app/pages/first-time-flow/end-of-flow/end-of-flow.component.js b/ui/app/pages/first-time-flow/end-of-flow/end-of-flow.component.js
index c4292331b..83b0e7fc6 100644
--- a/ui/app/pages/first-time-flow/end-of-flow/end-of-flow.component.js
+++ b/ui/app/pages/first-time-flow/end-of-flow/end-of-flow.component.js
@@ -71,7 +71,7 @@ export default class EndOfFlowScreen extends PureComponent {
</a>.
</div>
<Button
- type="confirm"
+ type="primary"
className="first-time-flow__button"
onClick={async () => {
await completeOnboarding()
diff --git a/ui/app/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js b/ui/app/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js
index 19c668278..6b9d06cf9 100644
--- a/ui/app/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js
+++ b/ui/app/pages/first-time-flow/metametrics-opt-in/metametrics-opt-in.component.js
@@ -119,7 +119,7 @@ export default class MetaMetricsOptIn extends Component {
hideCancel={false}
onSubmit={() => {
setParticipateInMetaMetrics(true)
- .then(([participateStatus, metaMetricsId]) => {
+ .then(([_, metaMetricsId]) => {
const promise = participateInMetaMetrics !== true
? metricsEvent({
eventOpts: {
@@ -149,7 +149,7 @@ export default class MetaMetricsOptIn extends Component {
})
}}
submitText={'I agree'}
- submitButtonType={'confirm'}
+ submitButtonType={'primary'}
disabled={false}
/>
<div className="metametrics-opt-in__bottom-text">
diff --git a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js
index 59b4f73a6..4cfc38fdf 100644
--- a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js
+++ b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.component.js
@@ -8,7 +8,9 @@ import {
INITIALIZE_SEED_PHRASE_ROUTE,
} from '../../../../helpers/constants/routes'
import { exportAsFile } from '../../../../helpers/utils/util'
-import { selectSeedWord, deselectSeedWord } from './confirm-seed-phrase.state'
+import DraggableSeed from './draggable-seed.component'
+
+const EMPTY_SEEDS = Array(12).fill(null)
export default class ConfirmSeedPhrase extends PureComponent {
static contextTypes = {
@@ -27,10 +29,32 @@ export default class ConfirmSeedPhrase extends PureComponent {
}
state = {
- selectedSeedWords: [],
+ selectedSeedIndices: [],
shuffledSeedWords: [],
- // Hash of shuffledSeedWords index {Number} to selectedSeedWords index {Number}
- selectedSeedWordsHash: {},
+ pendingSeedIndices: [],
+ draggingSeedIndex: -1,
+ hoveringIndex: -1,
+ isDragging: false,
+ }
+
+ shouldComponentUpdate (nextProps, nextState) {
+ const { seedPhrase } = this.props
+ const {
+ selectedSeedIndices,
+ shuffledSeedWords,
+ pendingSeedIndices,
+ draggingSeedIndex,
+ hoveringIndex,
+ isDragging,
+ } = this.state
+
+ return seedPhrase !== nextProps.seedPhrase ||
+ draggingSeedIndex !== nextState.draggingSeedIndex ||
+ isDragging !== nextState.isDragging ||
+ hoveringIndex !== nextState.hoveringIndex ||
+ selectedSeedIndices.join(' ') !== nextState.selectedSeedIndices.join(' ') ||
+ shuffledSeedWords.join(' ') !== nextState.shuffledSeedWords.join(' ') ||
+ pendingSeedIndices.join(' ') !== nextState.pendingSeedIndices.join(' ')
}
componentDidMount () {
@@ -39,6 +63,26 @@ export default class ConfirmSeedPhrase extends PureComponent {
this.setState({ shuffledSeedWords })
}
+ setDraggingSeedIndex = draggingSeedIndex => this.setState({ draggingSeedIndex })
+
+ setHoveringIndex = hoveringIndex => this.setState({ hoveringIndex })
+
+ onDrop = targetIndex => {
+ const {
+ selectedSeedIndices,
+ draggingSeedIndex,
+ } = this.state
+
+ const indices = insert(selectedSeedIndices, draggingSeedIndex, targetIndex, true)
+
+ this.setState({
+ selectedSeedIndices: indices,
+ pendingSeedIndices: indices,
+ draggingSeedIndex: -1,
+ hoveringIndex: -1,
+ })
+ }
+
handleExport = () => {
exportAsFile('MetaMask Secret Backup Phrase', this.props.seedPhrase, 'text/plain')
}
@@ -64,24 +108,35 @@ export default class ConfirmSeedPhrase extends PureComponent {
}
}
- handleSelectSeedWord = (word, shuffledIndex) => {
- this.setState(selectSeedWord(word, shuffledIndex))
+ handleSelectSeedWord = (shuffledIndex) => {
+ this.setState({
+ selectedSeedIndices: [...this.state.selectedSeedIndices, shuffledIndex],
+ pendingSeedIndices: [...this.state.pendingSeedIndices, shuffledIndex],
+ })
}
handleDeselectSeedWord = shuffledIndex => {
- this.setState(deselectSeedWord(shuffledIndex))
+ this.setState({
+ selectedSeedIndices: this.state.selectedSeedIndices.filter(i => shuffledIndex !== i),
+ pendingSeedIndices: this.state.pendingSeedIndices.filter(i => shuffledIndex !== i),
+ })
}
isValid () {
const { seedPhrase } = this.props
- const { selectedSeedWords } = this.state
+ const { selectedSeedIndices, shuffledSeedWords } = this.state
+ const selectedSeedWords = selectedSeedIndices.map(i => shuffledSeedWords[i])
return seedPhrase === selectedSeedWords.join(' ')
}
render () {
const { t } = this.context
const { history } = this.props
- const { selectedSeedWords, shuffledSeedWords, selectedSeedWordsHash } = this.state
+ const {
+ selectedSeedIndices,
+ shuffledSeedWords,
+ draggingSeedIndex,
+ } = this.state
return (
<div className="confirm-seed-phrase">
@@ -102,47 +157,45 @@ export default class ConfirmSeedPhrase extends PureComponent {
<div className="first-time-flow__text-block">
{ t('selectEachPhrase') }
</div>
- <div className="confirm-seed-phrase__selected-seed-words">
- {
- selectedSeedWords.map((word, index) => (
- <div
- key={index}
- className="confirm-seed-phrase__seed-word"
- >
- { word }
- </div>
- ))
- }
+ <div
+ className={classnames('confirm-seed-phrase__selected-seed-words', {
+ 'confirm-seed-phrase__selected-seed-words--dragging': draggingSeedIndex > -1,
+ })}
+ >
+ { this.renderPendingSeeds() }
+ { this.renderSelectedSeeds() }
</div>
<div className="confirm-seed-phrase__shuffled-seed-words">
{
shuffledSeedWords.map((word, index) => {
- const isSelected = index in selectedSeedWordsHash
+ const isSelected = selectedSeedIndices.includes(index)
return (
- <div
+ <DraggableSeed
key={index}
- className={classnames(
- 'confirm-seed-phrase__seed-word',
- 'confirm-seed-phrase__seed-word--shuffled',
- { 'confirm-seed-phrase__seed-word--selected': isSelected }
- )}
+ seedIndex={index}
+ index={index}
+ draggingSeedIndex={this.state.draggingSeedIndex}
+ setDraggingSeedIndex={this.setDraggingSeedIndex}
+ setHoveringIndex={this.setHoveringIndex}
+ onDrop={this.onDrop}
+ className="confirm-seed-phrase__seed-word--shuffled"
+ selected={isSelected}
onClick={() => {
if (!isSelected) {
- this.handleSelectSeedWord(word, index)
+ this.handleSelectSeedWord(index)
} else {
this.handleDeselectSeedWord(index)
}
}}
- >
- { word }
- </div>
+ word={word}
+ />
)
})
}
</div>
<Button
- type="confirm"
+ type="primary"
className="first-time-flow__button"
onClick={this.handleSubmit}
disabled={!this.isValid()}
@@ -152,4 +205,80 @@ export default class ConfirmSeedPhrase extends PureComponent {
</div>
)
}
+
+ renderSelectedSeeds () {
+ const { shuffledSeedWords, selectedSeedIndices, draggingSeedIndex } = this.state
+ return EMPTY_SEEDS.map((_, index) => {
+ const seedIndex = selectedSeedIndices[index]
+ const word = shuffledSeedWords[seedIndex]
+
+ return (
+ <DraggableSeed
+ key={`selected-${seedIndex}-${index}`}
+ className="confirm-seed-phrase__selected-seed-words__selected-seed"
+ index={index}
+ seedIndex={seedIndex}
+ word={word}
+ draggingSeedIndex={draggingSeedIndex}
+ setDraggingSeedIndex={this.setDraggingSeedIndex}
+ setHoveringIndex={this.setHoveringIndex}
+ onDrop={this.onDrop}
+ draggable
+ />
+ )
+ })
+ }
+
+ renderPendingSeeds () {
+ const {
+ pendingSeedIndices,
+ shuffledSeedWords,
+ draggingSeedIndex,
+ hoveringIndex,
+ } = this.state
+
+ const indices = insert(pendingSeedIndices, draggingSeedIndex, hoveringIndex)
+
+ return EMPTY_SEEDS.map((_, index) => {
+ const seedIndex = indices[index]
+ const word = shuffledSeedWords[seedIndex]
+
+ return (
+ <DraggableSeed
+ key={`pending-${seedIndex}-${index}`}
+ index={index}
+ className={classnames('confirm-seed-phrase__selected-seed-words__pending-seed', {
+ 'confirm-seed-phrase__seed-word--hidden': draggingSeedIndex === seedIndex && index !== hoveringIndex,
+ })}
+ seedIndex={seedIndex}
+ word={word}
+ draggingSeedIndex={draggingSeedIndex}
+ setDraggingSeedIndex={this.setDraggingSeedIndex}
+ setHoveringIndex={this.setHoveringIndex}
+ onDrop={this.onDrop}
+ droppable={!!word}
+ />
+ )
+ })
+ }
+}
+
+function insert (list, value, target, removeOld) {
+ let nextList = [...list]
+
+ if (typeof list[target] === 'number') {
+ nextList = [...list.slice(0, target), value, ...list.slice(target)]
+ }
+
+ if (removeOld) {
+ nextList = nextList.filter((seed, i) => {
+ return seed !== value || i === target
+ })
+ }
+
+ if (nextList.length > 12) {
+ nextList.pop()
+ }
+
+ return nextList
}
diff --git a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.state.js b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.state.js
deleted file mode 100644
index f2476fc5c..000000000
--- a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/confirm-seed-phrase.state.js
+++ /dev/null
@@ -1,41 +0,0 @@
-export function selectSeedWord (word, shuffledIndex) {
- return function update (state) {
- const { selectedSeedWords, selectedSeedWordsHash } = state
- const nextSelectedIndex = selectedSeedWords.length
-
- return {
- selectedSeedWords: [ ...selectedSeedWords, word ],
- selectedSeedWordsHash: { ...selectedSeedWordsHash, [shuffledIndex]: nextSelectedIndex },
- }
- }
-}
-
-export function deselectSeedWord (shuffledIndex) {
- return function update (state) {
- const {
- selectedSeedWords: prevSelectedSeedWords,
- selectedSeedWordsHash: prevSelectedSeedWordsHash,
- } = state
-
- const selectedSeedWords = [...prevSelectedSeedWords]
- const indexToRemove = prevSelectedSeedWordsHash[shuffledIndex]
- selectedSeedWords.splice(indexToRemove, 1)
- const selectedSeedWordsHash = Object.keys(prevSelectedSeedWordsHash).reduce((acc, index) => {
- const output = { ...acc }
- const selectedSeedWordIndex = prevSelectedSeedWordsHash[index]
-
- if (selectedSeedWordIndex < indexToRemove) {
- output[index] = selectedSeedWordIndex
- } else if (selectedSeedWordIndex > indexToRemove) {
- output[index] = selectedSeedWordIndex - 1
- }
-
- return output
- }, {})
-
- return {
- selectedSeedWords,
- selectedSeedWordsHash,
- }
- }
-}
diff --git a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/draggable-seed.component.js b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/draggable-seed.component.js
new file mode 100644
index 000000000..cdb881921
--- /dev/null
+++ b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/draggable-seed.component.js
@@ -0,0 +1,126 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import classnames from 'classnames'
+import { DragSource, DropTarget } from 'react-dnd'
+
+class DraggableSeed extends Component {
+
+ static propTypes = {
+ // React DnD Props
+ connectDragSource: PropTypes.func.isRequired,
+ connectDropTarget: PropTypes.func.isRequired,
+ isDragging: PropTypes.bool,
+ isOver: PropTypes.bool,
+ canDrop: PropTypes.bool,
+ // Own Props
+ onClick: PropTypes.func.isRequired,
+ setHoveringIndex: PropTypes.func.isRequired,
+ index: PropTypes.number,
+ draggingSeedIndex: PropTypes.number,
+ word: PropTypes.string,
+ className: PropTypes.string,
+ selected: PropTypes.bool,
+ droppable: PropTypes.bool,
+ }
+
+ static defaultProps = {
+ className: '',
+ onClick () {},
+ }
+
+ componentWillReceiveProps (nextProps) {
+ const { isOver, setHoveringIndex } = this.props
+ if (isOver && !nextProps.isOver) {
+ setHoveringIndex(-1)
+ }
+ }
+
+ render () {
+ const {
+ connectDragSource,
+ connectDropTarget,
+ isDragging,
+ index,
+ word,
+ selected,
+ className,
+ onClick,
+ isOver,
+ canDrop,
+ } = this.props
+
+ return connectDropTarget(connectDragSource(
+ <div
+ key={index}
+ className={classnames('btn-secondary confirm-seed-phrase__seed-word', className, {
+ 'confirm-seed-phrase__seed-word--selected btn-primary': selected,
+ 'confirm-seed-phrase__seed-word--dragging': isDragging,
+ 'confirm-seed-phrase__seed-word--empty': !word,
+ 'confirm-seed-phrase__seed-word--active-drop': !isOver && canDrop,
+ 'confirm-seed-phrase__seed-word--drop-hover': isOver && canDrop,
+ })}
+ onClick={onClick}
+ >
+ { word }
+ </div>
+ ))
+ }
+}
+
+const SEEDWORD = 'SEEDWORD'
+
+const seedSource = {
+ beginDrag (props) {
+ setTimeout(() => props.setDraggingSeedIndex(props.seedIndex), 0)
+ return {
+ seedIndex: props.seedIndex,
+ word: props.word,
+ }
+ },
+ canDrag (props) {
+ return props.draggable
+ },
+ endDrag (props, monitor) {
+ const dropTarget = monitor.getDropResult()
+
+ if (!dropTarget) {
+ setTimeout(() => props.setDraggingSeedIndex(-1), 0)
+ return
+ }
+
+ props.onDrop(dropTarget.targetIndex)
+ },
+}
+
+const seedTarget = {
+ drop (props) {
+ return {
+ targetIndex: props.index,
+ }
+ },
+ canDrop (props) {
+ return props.droppable
+ },
+ hover (props) {
+ props.setHoveringIndex(props.index)
+ },
+}
+
+const collectDrag = (connect, monitor) => {
+ return {
+ connectDragSource: connect.dragSource(),
+ isDragging: monitor.isDragging(),
+ }
+}
+
+const collectDrop = (connect, monitor) => {
+ return {
+ connectDropTarget: connect.dropTarget(),
+ isOver: monitor.isOver(),
+ canDrop: monitor.canDrop(),
+ }
+}
+
+export default DropTarget(SEEDWORD, seedTarget, collectDrop)(DragSource(SEEDWORD, seedSource, collectDrag)(DraggableSeed))
+
+
diff --git a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.scss b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.scss
index 93137618c..f025a503f 100644
--- a/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.scss
+++ b/ui/app/pages/first-time-flow/seed-phrase/confirm-seed-phrase/index.scss
@@ -3,37 +3,58 @@
margin-bottom: 12px;
}
- &__selected-seed-words {
- min-height: 190px;
- max-width: 496px;
- border: 1px solid #CDCDCD;
- border-radius: 6px;
- background-color: $white;
- margin: 24px 0 36px;
- padding: 12px;
- }
-
&__shuffled-seed-words {
- max-width: 496px;
+ max-width: 575px;
}
&__seed-word {
- display: inline-block;
- color: #5B5D67;
- background-color: #E7E7E7;
+ display: inline-flex;
+ flex-flow: row nowrap;
+ align-items: center;
+ justify-content: center;
padding: 8px 18px;
- min-width: 64px;
+ width: 128px;
+ height: 41px;
margin: 4px;
text-align: center;
+ border-radius: 4px;
+ cursor: move;
+
+ &--shuffled {
+ cursor: pointer;
+ margin: 6px;
+ }
&--selected {
- background-color: #85D1CC;
color: $white;
}
- &--shuffled {
- cursor: pointer;
- margin: 6px;
+ &--dragging {
+ margin: 0;
+ }
+
+ &--empty {
+ background-color: transparent;
+ border-color: transparent;
+ cursor: default;
+
+ &:hover,
+ &:active {
+ background-color: transparent;
+ border-color: transparent;
+ cursor: default;
+ box-shadow: none !important;
+ }
+ }
+
+ &--hidden {
+ display: none !important;
+ }
+
+ &--drop-hover {
+ background-color: transparent;
+ border-color: transparent;
+ color: transparent;
}
@media screen and (max-width: 575px) {
@@ -42,7 +63,37 @@
}
}
- button {
- margin-top: 0xp;
+ &__selected-seed-words {
+ display: flex;
+ flex-flow: row wrap;
+ min-height: 161px;
+ max-width: 575px;
+ border: 1px solid #CDCDCD;
+ border-radius: 6px;
+ background-color: $white;
+ margin: 24px 0 36px;
+ padding: 12px;
+
+ &__pending-seed {
+ display: none;
+ }
+
+ &__selected-seed {
+ display: inline-flex;
+
+ &:hover {
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.25);
+ }
+ }
+
+ &--dragging {
+ .confirm-seed-phrase__selected-seed-words__pending-seed {
+ display: inline-flex;
+ }
+
+ .confirm-seed-phrase__selected-seed-words__selected-seed {
+ display: none;
+ }
+ }
}
}
diff --git a/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js b/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js
index ee352d74e..4e9948a0e 100644
--- a/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js
+++ b/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js
@@ -130,7 +130,7 @@ export default class RevealSeedPhrase extends PureComponent {
</div>
</div>
<Button
- type="confirm"
+ type="primary"
className="first-time-flow__button"
onClick={this.handleNext}
disabled={!isShowingSeedPhrase}
diff --git a/ui/app/pages/first-time-flow/seed-phrase/seed-phrase.component.js b/ui/app/pages/first-time-flow/seed-phrase/seed-phrase.component.js
index 9a9f84049..0b19af18c 100644
--- a/ui/app/pages/first-time-flow/seed-phrase/seed-phrase.component.js
+++ b/ui/app/pages/first-time-flow/seed-phrase/seed-phrase.component.js
@@ -8,6 +8,8 @@ import {
INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE,
DEFAULT_ROUTE,
} from '../../../helpers/constants/routes'
+import HTML5Backend from 'react-dnd-html5-backend'
+import {DragDropContextProvider} from 'react-dnd'
export default class SeedPhrase extends PureComponent {
static propTypes = {
@@ -28,43 +30,45 @@ export default class SeedPhrase extends PureComponent {
const { seedPhrase } = this.props
return (
- <div className="first-time-flow__wrapper">
- <div className="app-header__logo-container">
- <img
- className="app-header__metafox-logo app-header__metafox-logo--horizontal"
- src="/images/logo/metamask-logo-horizontal.svg"
- height={30}
- />
- <img
- className="app-header__metafox-logo app-header__metafox-logo--icon"
- src="/images/logo/metamask-fox.svg"
- height={42}
- width={42}
- />
+ <DragDropContextProvider backend={HTML5Backend}>
+ <div className="first-time-flow__wrapper">
+ <div className="app-header__logo-container">
+ <img
+ className="app-header__metafox-logo app-header__metafox-logo--horizontal"
+ src="/images/logo/metamask-logo-horizontal.svg"
+ height={30}
+ />
+ <img
+ className="app-header__metafox-logo app-header__metafox-logo--icon"
+ src="/images/logo/metamask-fox.svg"
+ height={42}
+ width={42}
+ />
+ </div>
+ <Switch>
+ <Route
+ exact
+ path={INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE}
+ render={props => (
+ <ConfirmSeedPhrase
+ { ...props }
+ seedPhrase={seedPhrase}
+ />
+ )}
+ />
+ <Route
+ exact
+ path={INITIALIZE_SEED_PHRASE_ROUTE}
+ render={props => (
+ <RevealSeedPhrase
+ { ...props }
+ seedPhrase={seedPhrase}
+ />
+ )}
+ />
+ </Switch>
</div>
- <Switch>
- <Route
- exact
- path={INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE}
- render={props => (
- <ConfirmSeedPhrase
- { ...props }
- seedPhrase={seedPhrase}
- />
- )}
- />
- <Route
- exact
- path={INITIALIZE_SEED_PHRASE_ROUTE}
- render={props => (
- <RevealSeedPhrase
- { ...props }
- seedPhrase={seedPhrase}
- />
- )}
- />
- </Switch>
- </div>
+ </DragDropContextProvider>
)
}
}
diff --git a/ui/app/pages/first-time-flow/seed-phrase/tests/confirm-seed-phrase-component.test.js b/ui/app/pages/first-time-flow/seed-phrase/tests/confirm-seed-phrase-component.test.js
new file mode 100644
index 000000000..8339a6f6f
--- /dev/null
+++ b/ui/app/pages/first-time-flow/seed-phrase/tests/confirm-seed-phrase-component.test.js
@@ -0,0 +1,169 @@
+import React from 'react'
+import assert from 'assert'
+import { shallow } from 'enzyme'
+import sinon from 'sinon'
+import ConfirmSeedPhrase from '../confirm-seed-phrase/confirm-seed-phrase.component'
+
+function shallowRender (props = {}, context = {}) {
+ return shallow(
+ <ConfirmSeedPhrase {...props} />,
+ {
+ context: {
+ t: str => str + '_t',
+ ...context,
+ },
+ }
+ )
+}
+
+describe('ConfirmSeedPhrase Component', () => {
+ it('should render correctly', () => {
+ const root = shallowRender({
+ seedPhrase: '鼠 牛 虎 兔 龍 蛇 馬 羊 猴 雞 狗 豬',
+ })
+
+ assert.equal(
+ root.find('.confirm-seed-phrase__seed-word--shuffled').length,
+ 12,
+ 'should render 12 seed phrases'
+ )
+ })
+
+ it('should add/remove selected on click', () => {
+ const metricsEventSpy = sinon.spy()
+ const pushSpy = sinon.spy()
+ const root = shallowRender(
+ {
+ seedPhrase: '鼠 牛 虎 兔 龍 蛇 馬 羊 猴 雞 狗 豬',
+ history: { push: pushSpy },
+ },
+ {
+ metricsEvent: metricsEventSpy,
+ }
+ )
+
+ const seeds = root.find('.confirm-seed-phrase__seed-word--shuffled')
+
+ // Click on 3 seeds to add to selected
+ seeds.at(0).simulate('click')
+ seeds.at(1).simulate('click')
+ seeds.at(2).simulate('click')
+
+ assert.deepEqual(
+ root.state().selectedSeedIndices,
+ [0, 1, 2],
+ 'should add seed phrase to selected on click',
+ )
+
+ // Click on a selected seed to remove
+ root.state()
+ root.update()
+ root.state()
+ root.find('.confirm-seed-phrase__seed-word--shuffled').at(1).simulate('click')
+ assert.deepEqual(
+ root.state().selectedSeedIndices,
+ [0, 2],
+ 'should remove seed phrase from selected when click again',
+ )
+ })
+
+ it('should render correctly on hover', () => {
+ const metricsEventSpy = sinon.spy()
+ const pushSpy = sinon.spy()
+ const root = shallowRender(
+ {
+ seedPhrase: '鼠 牛 虎 兔 龍 蛇 馬 羊 猴 雞 狗 豬',
+ history: { push: pushSpy },
+ },
+ {
+ metricsEvent: metricsEventSpy,
+ }
+ )
+
+ const seeds = root.find('.confirm-seed-phrase__seed-word--shuffled')
+
+ // Click on 3 seeds to add to selected
+ seeds.at(0).simulate('click')
+ seeds.at(1).simulate('click')
+ seeds.at(2).simulate('click')
+
+ // Dragging Seed # 2 to 0 placeth
+ root.instance().setDraggingSeedIndex(2)
+ root.instance().setHoveringIndex(0)
+
+ root.update()
+
+ const pendingSeeds = root.find('.confirm-seed-phrase__selected-seed-words__pending-seed')
+
+ assert.equal(pendingSeeds.at(0).props().seedIndex, 2)
+ assert.equal(pendingSeeds.at(1).props().seedIndex, 0)
+ assert.equal(pendingSeeds.at(2).props().seedIndex, 1)
+ })
+
+ it('should insert seed in place on drop', () => {
+ const metricsEventSpy = sinon.spy()
+ const pushSpy = sinon.spy()
+ const root = shallowRender(
+ {
+ seedPhrase: '鼠 牛 虎 兔 龍 蛇 馬 羊 猴 雞 狗 豬',
+ history: { push: pushSpy },
+ },
+ {
+ metricsEvent: metricsEventSpy,
+ }
+ )
+
+ const seeds = root.find('.confirm-seed-phrase__seed-word--shuffled')
+
+ // Click on 3 seeds to add to selected
+ seeds.at(0).simulate('click')
+ seeds.at(1).simulate('click')
+ seeds.at(2).simulate('click')
+
+ // Drop Seed # 2 to 0 placeth
+ root.instance().setDraggingSeedIndex(2)
+ root.instance().setHoveringIndex(0)
+ root.instance().onDrop(0)
+
+ root.update()
+
+ assert.deepEqual(root.state().selectedSeedIndices, [2, 0, 1])
+ assert.deepEqual(root.state().pendingSeedIndices, [2, 0, 1])
+ })
+
+ it('should submit correctly', () => {
+ const originalSeed = ['鼠', '牛', '虎', '兔', '龍', '蛇', '馬', '羊', '猴', '雞', '狗', '豬']
+ const metricsEventSpy = sinon.spy()
+ const pushSpy = sinon.spy()
+ const root = shallowRender(
+ {
+ seedPhrase: '鼠 牛 虎 兔 龍 蛇 馬 羊 猴 雞 狗 豬',
+ history: { push: pushSpy },
+ },
+ {
+ metricsEvent: metricsEventSpy,
+ }
+ )
+
+ const shuffled = root.state().shuffledSeedWords
+ const seeds = root.find('.confirm-seed-phrase__seed-word--shuffled')
+
+
+ originalSeed.forEach(seed => {
+ const seedIndex = shuffled.findIndex(s => s === seed)
+ seeds.at(seedIndex).simulate('click')
+ })
+
+ root.update()
+
+ root.find('.first-time-flow__button').simulate('click')
+ assert.deepEqual(metricsEventSpy.args[0][0], {
+ eventOpts: {
+ category: 'Onboarding',
+ action: 'Seed Phrase Setup',
+ name: 'Verify Complete',
+ },
+ })
+ assert.equal(pushSpy.args[0][0], '/initialize/end-of-flow')
+ })
+})
diff --git a/ui/app/pages/first-time-flow/select-action/select-action.component.js b/ui/app/pages/first-time-flow/select-action/select-action.component.js
index b25a15514..5af29a505 100644
--- a/ui/app/pages/first-time-flow/select-action/select-action.component.js
+++ b/ui/app/pages/first-time-flow/select-action/select-action.component.js
@@ -95,7 +95,7 @@ export default class SelectAction extends PureComponent {
</div>
</div>
<Button
- type="confirm"
+ type="primary"
className="first-time-flow__button"
onClick={this.handleCreate}
>
diff --git a/ui/app/pages/first-time-flow/welcome/welcome.component.js b/ui/app/pages/first-time-flow/welcome/welcome.component.js
index 3b8d6eb17..c720d2572 100644
--- a/ui/app/pages/first-time-flow/welcome/welcome.component.js
+++ b/ui/app/pages/first-time-flow/welcome/welcome.component.js
@@ -56,7 +56,7 @@ export default class Welcome extends PureComponent {
<div>{ t('happyToSeeYou') }</div>
</div>
<Button
- type="confirm"
+ type="primary"
className="first-time-flow__button"
onClick={this.handleContinue}
>
diff --git a/ui/app/pages/home/home.component.js b/ui/app/pages/home/home.component.js
index 29d93a9fa..4d96c3131 100644
--- a/ui/app/pages/home/home.component.js
+++ b/ui/app/pages/home/home.component.js
@@ -23,21 +23,27 @@ export default class Home extends PureComponent {
providerRequests: PropTypes.array,
}
+ componentWillMount () {
+ const {
+ history,
+ unconfirmedTransactionsCount = 0,
+ } = this.props
+
+ if (unconfirmedTransactionsCount > 0) {
+ history.push(CONFIRM_TRANSACTION_ROUTE)
+ }
+ }
+
componentDidMount () {
const {
history,
suggestedTokens = {},
- unconfirmedTransactionsCount = 0,
} = this.props
// suggested new tokens
if (Object.keys(suggestedTokens).length > 0) {
history.push(CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE)
}
-
- if (unconfirmedTransactionsCount > 0) {
- history.push(CONFIRM_TRANSACTION_ROUTE)
- }
}
render () {
@@ -45,6 +51,7 @@ export default class Home extends PureComponent {
forgottenPassword,
seedWords,
providerRequests,
+ history,
} = this.props
// seed words
@@ -69,7 +76,7 @@ export default class Home extends PureComponent {
query="(min-width: 576px)"
render={() => <WalletView />}
/>
- <TransactionView />
+ { !history.location.pathname.match(/^\/confirm-transaction/) ? <TransactionView /> : null }
</div>
</div>
)
diff --git a/ui/app/pages/home/home.container.js b/ui/app/pages/home/home.container.js
index 7508654dc..d0a5d7b47 100644
--- a/ui/app/pages/home/home.container.js
+++ b/ui/app/pages/home/home.container.js
@@ -3,7 +3,7 @@ import { compose } from 'recompose'
import { connect } from 'react-redux'
import { withRouter } from 'react-router-dom'
import { unconfirmedTransactionsCountSelector } from '../../selectors/confirm-transaction'
-
+``
const mapStateToProps = state => {
const { metamask, appState } = state
const {
diff --git a/ui/app/pages/keychains/reveal-seed.js b/ui/app/pages/keychains/reveal-seed.js
index edc9db5a0..e83e3fd98 100644
--- a/ui/app/pages/keychains/reveal-seed.js
+++ b/ui/app/pages/keychains/reveal-seed.js
@@ -116,7 +116,7 @@ class RevealSeedPage extends Component {
onClick: () => this.props.history.push(DEFAULT_ROUTE),
}, this.context.t('cancel')),
h(Button, {
- type: 'primary',
+ type: 'secondary',
large: true,
className: 'page-container__footer-button',
onClick: event => this.handleSubmit(event),
diff --git a/ui/app/pages/mobile-sync/index.js b/ui/app/pages/mobile-sync/index.js
index 0938ad103..00a514534 100644
--- a/ui/app/pages/mobile-sync/index.js
+++ b/ui/app/pages/mobile-sync/index.js
@@ -315,7 +315,7 @@ class MobileSyncPage extends Component {
}, this.context.t('cancel')),
h(Button, {
- type: 'primary',
+ type: 'secondary',
large: true,
className: 'new-account-create-form__button',
onClick: event => this.handleSubmit(event),
diff --git a/ui/app/pages/provider-approval/provider-approval.component.js b/ui/app/pages/provider-approval/provider-approval.component.js
index 1f1d68da7..70d3d0007 100644
--- a/ui/app/pages/provider-approval/provider-approval.component.js
+++ b/ui/app/pages/provider-approval/provider-approval.component.js
@@ -4,9 +4,9 @@ import ProviderPageContainer from '../../components/app/provider-page-container'
export default class ProviderApproval extends Component {
static propTypes = {
- approveProviderRequest: PropTypes.func.isRequired,
+ approveProviderRequestByOrigin: PropTypes.func.isRequired,
+ rejectProviderRequestByOrigin: PropTypes.func.isRequired,
providerRequest: PropTypes.object.isRequired,
- rejectProviderRequest: PropTypes.func.isRequired,
};
static contextTypes = {
@@ -14,13 +14,13 @@ export default class ProviderApproval extends Component {
};
render () {
- const { approveProviderRequest, providerRequest, rejectProviderRequest } = this.props
+ const { approveProviderRequestByOrigin, providerRequest, rejectProviderRequestByOrigin } = this.props
return (
<ProviderPageContainer
- approveProviderRequest={approveProviderRequest}
+ approveProviderRequestByOrigin={approveProviderRequestByOrigin}
+ rejectProviderRequestByOrigin={rejectProviderRequestByOrigin}
origin={providerRequest.origin}
tabID={providerRequest.tabID}
- rejectProviderRequest={rejectProviderRequest}
siteImage={providerRequest.siteImage}
siteTitle={providerRequest.siteTitle}
/>
diff --git a/ui/app/pages/provider-approval/provider-approval.container.js b/ui/app/pages/provider-approval/provider-approval.container.js
index d53c0ae4d..1e167ddb7 100644
--- a/ui/app/pages/provider-approval/provider-approval.container.js
+++ b/ui/app/pages/provider-approval/provider-approval.container.js
@@ -1,11 +1,11 @@
import { connect } from 'react-redux'
import ProviderApproval from './provider-approval.component'
-import { approveProviderRequest, rejectProviderRequest } from '../../store/actions'
+import { approveProviderRequestByOrigin, rejectProviderRequestByOrigin } from '../../store/actions'
function mapDispatchToProps (dispatch) {
return {
- approveProviderRequest: tabID => dispatch(approveProviderRequest(tabID)),
- rejectProviderRequest: tabID => dispatch(rejectProviderRequest(tabID)),
+ approveProviderRequestByOrigin: origin => dispatch(approveProviderRequestByOrigin(origin)),
+ rejectProviderRequestByOrigin: origin => dispatch(rejectProviderRequestByOrigin(origin)),
}
}
diff --git a/ui/app/pages/routes/index.js b/ui/app/pages/routes/index.js
index e06d88c90..9eeac2da2 100644
--- a/ui/app/pages/routes/index.js
+++ b/ui/app/pages/routes/index.js
@@ -5,12 +5,13 @@ import { Route, Switch, withRouter, matchPath } from 'react-router-dom'
import { compose } from 'recompose'
import actions from '../../store/actions'
import log from 'loglevel'
-import { getMetaMaskAccounts, getNetworkIdentifier } from '../../selectors/selectors'
+import IdleTimer from 'react-idle-timer'
+import {getMetaMaskAccounts, getNetworkIdentifier, preferencesSelector} from '../../selectors/selectors'
// init
import FirstTimeFlow from '../first-time-flow'
// accounts
-const SendTransactionScreen = require('../../components/app/send/send.container')
+const SendTransactionScreen = require('../send/send.container')
const ConfirmTransaction = require('../confirm-transaction')
// slideout menu
@@ -98,7 +99,9 @@ class Routes extends Component {
}
renderRoutes () {
- return (
+ const { autoLogoutTimeLimit, setLastActiveTime } = this.props
+
+ const routes = (
<Switch>
<Route path={LOCK_ROUTE} component={Lock} exact />
<Route path={INITIALIZE_ROUTE} component={FirstTimeFlow} />
@@ -116,6 +119,16 @@ class Routes extends Component {
<Authenticated path={DEFAULT_ROUTE} component={Home} exact />
</Switch>
)
+
+ if (autoLogoutTimeLimit > 0) {
+ return (
+ <IdleTimer onAction={setLastActiveTime} throttle={1000}>
+ {routes}
+ </IdleTimer>
+ )
+ }
+
+ return routes
}
onInitializationUnlockPage () {
@@ -267,6 +280,10 @@ class Routes extends Component {
name = this.context.t('connectingToKovan')
} else if (providerName === 'rinkeby') {
name = this.context.t('connectingToRinkeby')
+ } else if (providerName === 'localhost') {
+ name = this.context.t('connectingToLocalhost')
+ } else if (providerName === 'goerli') {
+ name = this.context.t('connectingToGoerli')
} else {
name = this.context.t('connectingTo', [providerId])
}
@@ -288,6 +305,10 @@ class Routes extends Component {
name = this.context.t('kovan')
} else if (providerName === 'rinkeby') {
name = this.context.t('rinkeby')
+ } else if (providerName === 'localhost') {
+ name = this.context.t('localhost')
+ } else if (providerName === 'goerli') {
+ name = this.context.t('goerli')
} else {
name = this.context.t('unknownNetwork')
}
@@ -314,6 +335,7 @@ Routes.propTypes = {
networkDropdownOpen: PropTypes.bool,
showNetworkDropdown: PropTypes.func,
hideNetworkDropdown: PropTypes.func,
+ setLastActiveTime: PropTypes.func,
history: PropTypes.object,
location: PropTypes.object,
dispatch: PropTypes.func,
@@ -336,6 +358,7 @@ Routes.propTypes = {
t: PropTypes.func,
providerId: PropTypes.string,
providerRequests: PropTypes.array,
+ autoLogoutTimeLimit: PropTypes.number,
}
function mapStateToProps (state) {
@@ -350,6 +373,7 @@ function mapStateToProps (state) {
} = appState
const accounts = getMetaMaskAccounts(state)
+ const { autoLogoutTimeLimit = 0 } = preferencesSelector(state)
const {
identities,
@@ -401,6 +425,7 @@ function mapStateToProps (state) {
Qr: state.appState.Qr,
welcomeScreenSeen: state.metamask.welcomeScreenSeen,
providerId: getNetworkIdentifier(state),
+ autoLogoutTimeLimit,
// state needed to get account dropdown temporarily rendering from app bar
identities,
@@ -410,7 +435,7 @@ function mapStateToProps (state) {
}
}
-function mapDispatchToProps (dispatch, ownProps) {
+function mapDispatchToProps (dispatch) {
return {
dispatch,
hideSidebar: () => dispatch(actions.hideSidebar()),
@@ -419,6 +444,7 @@ function mapDispatchToProps (dispatch, ownProps) {
setCurrentCurrencyToUSD: () => dispatch(actions.setCurrentCurrency('usd')),
toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()),
setMouseUserState: (isMouseUser) => dispatch(actions.setMouseUserState(isMouseUser)),
+ setLastActiveTime: () => dispatch(actions.setLastActiveTime()),
}
}
diff --git a/ui/app/components/app/send/README.md b/ui/app/pages/send/README.md
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/app/send/README.md
+++ b/ui/app/pages/send/README.md
diff --git a/ui/app/components/app/send/account-list-item/account-list-item-README.md b/ui/app/pages/send/account-list-item/account-list-item-README.md
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/app/send/account-list-item/account-list-item-README.md
+++ b/ui/app/pages/send/account-list-item/account-list-item-README.md
diff --git a/ui/app/components/app/send/account-list-item/account-list-item.component.js b/ui/app/pages/send/account-list-item/account-list-item.component.js
index 18e77b4f9..e6cca39b9 100644
--- a/ui/app/components/app/send/account-list-item/account-list-item.component.js
+++ b/ui/app/pages/send/account-list-item/account-list-item.component.js
@@ -1,11 +1,11 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
-import { checksumAddress } from '../../../../helpers/utils/util'
-import Identicon from '../../../ui/identicon'
-import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display'
-import { PRIMARY, SECONDARY } from '../../../../helpers/constants/common'
-import Tooltip from '../../../ui/tooltip-v2'
+import { checksumAddress } from '../../../helpers/utils/util'
+import Identicon from '../../../components/ui/identicon'
+import UserPreferencedCurrencyDisplay from '../../../components/app/user-preferenced-currency-display'
+import { PRIMARY, SECONDARY } from '../../../helpers/constants/common'
+import Tooltip from '../../../components/ui/tooltip-v2'
export default class AccountListItem extends Component {
diff --git a/ui/app/components/app/send/account-list-item/account-list-item.container.js b/ui/app/pages/send/account-list-item/account-list-item.container.js
index bc9a60f49..21f800306 100644
--- a/ui/app/components/app/send/account-list-item/account-list-item.container.js
+++ b/ui/app/pages/send/account-list-item/account-list-item.container.js
@@ -8,7 +8,7 @@ import {
getIsMainnet,
isBalanceCached,
preferencesSelector,
-} from '../../../../selectors/selectors'
+} from '../../../selectors/selectors'
import AccountListItem from './account-list-item.component'
export default connect(mapStateToProps)(AccountListItem)
diff --git a/ui/app/components/app/send/account-list-item/index.js b/ui/app/pages/send/account-list-item/index.js
index 907485cf7..907485cf7 100644
--- a/ui/app/components/app/send/account-list-item/index.js
+++ b/ui/app/pages/send/account-list-item/index.js
diff --git a/ui/app/components/app/send/account-list-item/tests/account-list-item-component.test.js b/ui/app/pages/send/account-list-item/tests/account-list-item-component.test.js
index 5df9f77d6..bec88402d 100644
--- a/ui/app/components/app/send/account-list-item/tests/account-list-item-component.test.js
+++ b/ui/app/pages/send/account-list-item/tests/account-list-item-component.test.js
@@ -3,15 +3,15 @@ import assert from 'assert'
import { shallow } from 'enzyme'
import sinon from 'sinon'
import proxyquire from 'proxyquire'
-import Identicon from '../../../../ui/identicon'
-import UserPreferencedCurrencyDisplay from '../../../user-preferenced-currency-display'
+import Identicon from '../../../../components/ui/identicon'
+import UserPreferencedCurrencyDisplay from '../../../../components/app/user-preferenced-currency-display'
const utilsMethodStubs = {
checksumAddress: sinon.stub().returns('mockCheckSumAddress'),
}
const AccountListItem = proxyquire('../account-list-item.component.js', {
- '../../../../helpers/utils/util': utilsMethodStubs,
+ '../../../helpers/utils/util': utilsMethodStubs,
}).default
diff --git a/ui/app/components/app/send/account-list-item/tests/account-list-item-container.test.js b/ui/app/pages/send/account-list-item/tests/account-list-item-container.test.js
index 19a9a02d0..1580fd497 100644
--- a/ui/app/components/app/send/account-list-item/tests/account-list-item-container.test.js
+++ b/ui/app/pages/send/account-list-item/tests/account-list-item-container.test.js
@@ -5,7 +5,7 @@ let mapStateToProps
proxyquire('../account-list-item.container.js', {
'react-redux': {
- connect: (ms, md) => {
+ connect: (ms) => {
mapStateToProps = ms
return () => ({})
},
@@ -15,7 +15,7 @@ proxyquire('../account-list-item.container.js', {
getCurrentCurrency: () => `mockCurrentCurrency`,
getNativeCurrency: () => `mockNativeCurrency`,
},
- '../../../../selectors/selectors': {
+ '../../../selectors/selectors': {
isBalanceCached: () => `mockBalanceIsCached`,
preferencesSelector: ({ showFiatInTestnets }) => ({
showFiatInTestnets,
diff --git a/ui/app/components/app/send/index.js b/ui/app/pages/send/index.js
index b5114babc..b5114babc 100644
--- a/ui/app/components/app/send/index.js
+++ b/ui/app/pages/send/index.js
diff --git a/ui/app/components/app/send/send-content/index.js b/ui/app/pages/send/send-content/index.js
index 891c17e6a..891c17e6a 100644
--- a/ui/app/components/app/send/send-content/index.js
+++ b/ui/app/pages/send/send-content/index.js
diff --git a/ui/app/components/app/send/send-content/send-amount-row/README.md b/ui/app/pages/send/send-content/send-amount-row/README.md
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/app/send/send-content/send-amount-row/README.md
+++ b/ui/app/pages/send/send-content/send-amount-row/README.md
diff --git a/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js
new file mode 100644
index 000000000..7901ccef6
--- /dev/null
+++ b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.component.js
@@ -0,0 +1,75 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import classnames from 'classnames'
+
+export default class AmountMaxButton extends Component {
+
+ static propTypes = {
+ balance: PropTypes.string,
+ buttonDataLoading: PropTypes.bool,
+ clearMaxAmount: PropTypes.func,
+ inError: PropTypes.bool,
+ gasTotal: PropTypes.string,
+ maxModeOn: PropTypes.bool,
+ selectedToken: PropTypes.object,
+ setAmountToMax: PropTypes.func,
+ setMaxModeTo: PropTypes.func,
+ tokenBalance: PropTypes.string,
+
+ }
+
+ static contextTypes = {
+ t: PropTypes.func,
+ metricsEvent: PropTypes.func,
+ }
+
+ setMaxAmount () {
+ const {
+ balance,
+ gasTotal,
+ selectedToken,
+ setAmountToMax,
+ tokenBalance,
+ } = this.props
+
+ setAmountToMax({
+ balance,
+ gasTotal,
+ selectedToken,
+ tokenBalance,
+ })
+ }
+
+ onMaxClick = () => {
+ const { setMaxModeTo, clearMaxAmount, maxModeOn } = this.props
+ const { metricsEvent } = this.context
+
+ metricsEvent({
+ eventOpts: {
+ category: 'Transactions',
+ action: 'Edit Screen',
+ name: 'Clicked "Amount Max"',
+ },
+ })
+ if (!maxModeOn) {
+ setMaxModeTo(true)
+ this.setMaxAmount()
+ } else {
+ setMaxModeTo(false)
+ clearMaxAmount()
+ }
+ }
+
+ render () {
+ const { maxModeOn, buttonDataLoading, inError } = this.props
+
+ return (
+ <div className={'send-v2__amount-max'} onClick={buttonDataLoading || inError ? null : this.onMaxClick}>
+ <input type="checkbox" checked={maxModeOn} />
+ <div className={classnames('send-v2__amount-max__button', { 'send-v2__amount-max__button__disabled': buttonDataLoading || inError })}>
+ {this.context.t('max')}
+ </div>
+ </div>
+ )
+ }
+}
diff --git a/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/amount-max-button.container.js b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.container.js
index 16c5a0db5..e444589a1 100644
--- a/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/amount-max-button.container.js
+++ b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.container.js
@@ -5,16 +5,17 @@ import {
getSendFromBalance,
getTokenBalance,
} from '../../../send.selectors.js'
+import { getBasicGasEstimateLoadingStatus } from '../../../../../selectors/custom-gas'
import { getMaxModeOn } from './amount-max-button.selectors.js'
import { calcMaxAmount } from './amount-max-button.utils.js'
import {
updateSendAmount,
setMaxModeTo,
-} from '../../../../../../store/actions'
+} from '../../../../../store/actions'
import AmountMaxButton from './amount-max-button.component'
import {
updateSendErrors,
-} from '../../../../../../ducks/send/send.duck'
+} from '../../../../../ducks/send/send.duck'
export default connect(mapStateToProps, mapDispatchToProps)(AmountMaxButton)
@@ -22,6 +23,7 @@ function mapStateToProps (state) {
return {
balance: getSendFromBalance(state),
+ buttonDataLoading: getBasicGasEstimateLoadingStatus(state),
gasTotal: getGasTotal(state),
maxModeOn: getMaxModeOn(state),
selectedToken: getSelectedToken(state),
@@ -35,6 +37,9 @@ function mapDispatchToProps (dispatch) {
dispatch(updateSendErrors({ amount: null }))
dispatch(updateSendAmount(calcMaxAmount(maxAmountDataObject)))
},
+ clearMaxAmount: () => {
+ dispatch(updateSendAmount('0'))
+ },
setMaxModeTo: bool => dispatch(setMaxModeTo(bool)),
}
}
diff --git a/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/amount-max-button.selectors.js b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.selectors.js
index 69fec1994..69fec1994 100644
--- a/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/amount-max-button.selectors.js
+++ b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.selectors.js
diff --git a/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js
index f4c8fad8a..a570e49b4 100644
--- a/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js
+++ b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/amount-max-button.utils.js
@@ -1,7 +1,7 @@
const {
multiplyCurrencies,
subtractCurrencies,
-} = require('../../../../../../helpers/utils/conversion-util')
+} = require('../../../../../helpers/utils/conversion-util')
const ethUtil = require('ethereumjs-util')
function calcMaxAmount ({ balance, gasTotal, selectedToken, tokenBalance }) {
diff --git a/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/index.js b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/index.js
index ee8271494..ee8271494 100644
--- a/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/index.js
+++ b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/index.js
diff --git a/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js
index b04d3897f..f986b26bb 100644
--- a/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js
+++ b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-component.test.js
@@ -26,7 +26,12 @@ describe('AmountMaxButton Component', function () {
setAmountToMax={propsMethodSpies.setAmountToMax}
setMaxModeTo={propsMethodSpies.setMaxModeTo}
tokenBalance={'mockTokenBalance'}
- />, { context: { t: str => str + '_t' } })
+ />, {
+ context: {
+ t: str => str + '_t',
+ metricsEvent: () => {},
+ },
+ })
instance = wrapper.instance()
})
@@ -60,7 +65,7 @@ describe('AmountMaxButton Component', function () {
assert(wrapper.exists('.send-v2__amount-max'))
})
- it('should call setMaxModeTo and setMaxAmount when the send-v2__amount-max div is clicked', () => {
+ it('should call setMaxModeTo and setMaxAmount when the checkbox is checked', () => {
const {
onClick,
} = wrapper.find('.send-v2__amount-max').props()
@@ -76,11 +81,6 @@ describe('AmountMaxButton Component', function () {
)
})
- it('should not render anything when maxModeOn is true', () => {
- wrapper.setProps({ maxModeOn: true })
- assert.ok(!wrapper.exists('.send-v2__amount-max'))
- })
-
it('should render the expected text when maxModeOn is false', () => {
wrapper.setProps({ maxModeOn: false })
assert.equal(wrapper.find('.send-v2__amount-max').text(), 'max_t')
diff --git a/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js
index f446e330c..dcee8fda0 100644
--- a/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js
+++ b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-container.test.js
@@ -29,8 +29,9 @@ proxyquire('../amount-max-button.container.js', {
},
'./amount-max-button.selectors.js': { getMaxModeOn: (s) => `mockMaxModeOn:${s}` },
'./amount-max-button.utils.js': { calcMaxAmount: (mockObj) => mockObj.val + 1 },
- '../../../../../../store/actions': actionSpies,
- '../../../../../../ducks/send/send.duck': duckActionSpies,
+ '../../../../../selectors/custom-gas': { getBasicGasEstimateLoadingStatus: (s) => `mockButtonDataLoading:${s}`},
+ '../../../../../store/actions': actionSpies,
+ '../../../../../ducks/send/send.duck': duckActionSpies,
})
describe('amount-max-button container', () => {
@@ -40,6 +41,7 @@ describe('amount-max-button container', () => {
it('should map the correct properties to props', () => {
assert.deepEqual(mapStateToProps('mockState'), {
balance: 'mockBalance:mockState',
+ buttonDataLoading: 'mockButtonDataLoading:mockState',
gasTotal: 'mockGasTotal:mockState',
maxModeOn: 'mockMaxModeOn:mockState',
selectedToken: 'mockSelectedToken:mockState',
diff --git a/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-selectors.test.js b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-selectors.test.js
index 655fe1969..655fe1969 100644
--- a/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-selectors.test.js
+++ b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-selectors.test.js
diff --git a/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-utils.test.js b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-utils.test.js
index 1ee858f67..1ee858f67 100644
--- a/ui/app/components/app/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-utils.test.js
+++ b/ui/app/pages/send/send-content/send-amount-row/amount-max-button/tests/amount-max-button-utils.test.js
diff --git a/ui/app/components/app/send/send-content/send-amount-row/index.js b/ui/app/pages/send/send-content/send-amount-row/index.js
index abc6852fe..abc6852fe 100644
--- a/ui/app/components/app/send/send-content/send-amount-row/index.js
+++ b/ui/app/pages/send/send-content/send-amount-row/index.js
diff --git a/ui/app/components/app/send/send-content/send-amount-row/send-amount-row.component.js b/ui/app/pages/send/send-content/send-amount-row/send-amount-row.component.js
index e725e7eda..10e90c419 100644
--- a/ui/app/components/app/send/send-content/send-amount-row/send-amount-row.component.js
+++ b/ui/app/pages/send/send-content/send-amount-row/send-amount-row.component.js
@@ -2,8 +2,8 @@ import React, { Component } from 'react'
import PropTypes from 'prop-types'
import SendRowWrapper from '../send-row-wrapper'
import AmountMaxButton from './amount-max-button'
-import UserPreferencedCurrencyInput from '../../../user-preferenced-currency-input'
-import UserPreferencedTokenInput from '../../../user-preferenced-token-input'
+import UserPreferencedCurrencyInput from '../../../../components/app/user-preferenced-currency-input'
+import UserPreferencedTokenInput from '../../../../components/app/user-preferenced-token-input'
export default class SendAmountRow extends Component {
@@ -110,7 +110,7 @@ export default class SendAmountRow extends Component {
showError={inError}
errorType={'amount'}
>
- {!inError && gasTotal && <AmountMaxButton />}
+ {gasTotal && <AmountMaxButton inError={inError} />}
{ this.renderInput() }
</SendRowWrapper>
)
diff --git a/ui/app/components/app/send/send-content/send-amount-row/send-amount-row.container.js b/ui/app/pages/send/send-content/send-amount-row/send-amount-row.container.js
index 0646355ab..2b3470da4 100644
--- a/ui/app/components/app/send/send-content/send-amount-row/send-amount-row.container.js
+++ b/ui/app/pages/send/send-content/send-amount-row/send-amount-row.container.js
@@ -17,10 +17,10 @@ import { getAmountErrorObject, getGasFeeErrorObject } from '../../send.utils'
import {
setMaxModeTo,
updateSendAmount,
-} from '../../../../../store/actions'
+} from '../../../../store/actions'
import {
updateSendErrors,
-} from '../../../../../ducks/send/send.duck'
+} from '../../../../ducks/send/send.duck'
import SendAmountRow from './send-amount-row.component'
export default connect(mapStateToProps, mapDispatchToProps)(SendAmountRow)
diff --git a/ui/app/components/app/send/send-content/send-amount-row/send-amount-row.scss b/ui/app/pages/send/send-content/send-amount-row/send-amount-row.scss
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/app/send/send-content/send-amount-row/send-amount-row.scss
+++ b/ui/app/pages/send/send-content/send-amount-row/send-amount-row.scss
diff --git a/ui/app/components/app/send/send-content/send-amount-row/send-amount-row.selectors.js b/ui/app/pages/send/send-content/send-amount-row/send-amount-row.selectors.js
index fb08c7ed7..fb08c7ed7 100644
--- a/ui/app/components/app/send/send-content/send-amount-row/send-amount-row.selectors.js
+++ b/ui/app/pages/send/send-content/send-amount-row/send-amount-row.selectors.js
diff --git a/ui/app/components/app/send/send-content/send-amount-row/tests/send-amount-row-component.test.js b/ui/app/pages/send/send-content/send-amount-row/tests/send-amount-row-component.test.js
index 14a71129f..62e0676db 100644
--- a/ui/app/components/app/send/send-content/send-amount-row/tests/send-amount-row-component.test.js
+++ b/ui/app/pages/send/send-content/send-amount-row/tests/send-amount-row-component.test.js
@@ -6,7 +6,7 @@ import SendAmountRow from '../send-amount-row.component.js'
import SendRowWrapper from '../../send-row-wrapper/send-row-wrapper.component'
import AmountMaxButton from '../amount-max-button/amount-max-button.container'
-import UserPreferencedTokenInput from '../../../../user-preferenced-token-input'
+import UserPreferencedTokenInput from '../../../../../components/app/user-preferenced-token-input'
const propsMethodSpies = {
setMaxModeTo: sinon.spy(),
diff --git a/ui/app/components/app/send/send-content/send-amount-row/tests/send-amount-row-container.test.js b/ui/app/pages/send/send-content/send-amount-row/tests/send-amount-row-container.test.js
index 6d20202b0..dada1c5e9 100644
--- a/ui/app/components/app/send/send-content/send-amount-row/tests/send-amount-row-container.test.js
+++ b/ui/app/pages/send/send-content/send-amount-row/tests/send-amount-row-container.test.js
@@ -37,8 +37,8 @@ proxyquire('../send-amount-row.container.js', {
getAmountErrorObject: (mockDataObject) => ({ ...mockDataObject, mockChange: true }),
getGasFeeErrorObject: (mockDataObject) => ({ ...mockDataObject, mockGasFeeErrorChange: true }),
},
- '../../../../../store/actions': actionSpies,
- '../../../../../ducks/send/send.duck': duckActionSpies,
+ '../../../../store/actions': actionSpies,
+ '../../../../ducks/send/send.duck': duckActionSpies,
})
describe('send-amount-row container', () => {
diff --git a/ui/app/components/app/send/send-content/send-amount-row/tests/send-amount-row-selectors.test.js b/ui/app/pages/send/send-content/send-amount-row/tests/send-amount-row-selectors.test.js
index 4672cb8a7..4672cb8a7 100644
--- a/ui/app/components/app/send/send-content/send-amount-row/tests/send-amount-row-selectors.test.js
+++ b/ui/app/pages/send/send-content/send-amount-row/tests/send-amount-row-selectors.test.js
diff --git a/ui/app/pages/send/send-content/send-asset-row/index.js b/ui/app/pages/send/send-content/send-asset-row/index.js
new file mode 100644
index 000000000..ba424a083
--- /dev/null
+++ b/ui/app/pages/send/send-content/send-asset-row/index.js
@@ -0,0 +1 @@
+export { default } from './send-asset-row.container'
diff --git a/ui/app/pages/send/send-content/send-asset-row/send-asset-row.component.js b/ui/app/pages/send/send-content/send-asset-row/send-asset-row.component.js
new file mode 100644
index 000000000..de2d9462f
--- /dev/null
+++ b/ui/app/pages/send/send-content/send-asset-row/send-asset-row.component.js
@@ -0,0 +1,152 @@
+import React, { Component } from 'react'
+import PropTypes from 'prop-types'
+import SendRowWrapper from '../send-row-wrapper'
+import Identicon from '../../../../components/ui/identicon/identicon.component'
+import TokenBalance from '../../../../components/ui/token-balance'
+import UserPreferencedCurrencyDisplay from '../../../../components/app/user-preferenced-currency-display'
+import {PRIMARY} from '../../../../helpers/constants/common'
+
+export default class SendAssetRow extends Component {
+ static propTypes = {
+ tokens: PropTypes.arrayOf(
+ PropTypes.shape({
+ address: PropTypes.string,
+ decimals: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ symbol: PropTypes.string,
+ })
+ ).isRequired,
+ accounts: PropTypes.object.isRequired,
+ selectedAddress: PropTypes.string.isRequired,
+ selectedTokenAddress: PropTypes.string,
+ setSelectedToken: PropTypes.func.isRequired,
+ }
+
+ static contextTypes = {
+ t: PropTypes.func,
+ metricsEvent: PropTypes.func,
+ }
+
+ state = {
+ isShowingDropdown: false,
+ }
+
+ openDropdown = () => this.setState({ isShowingDropdown: true })
+
+ closeDropdown = () => this.setState({ isShowingDropdown: false })
+
+ selectToken = address => {
+ this.setState({
+ isShowingDropdown: false,
+ }, () => {
+ this.context.metricsEvent({
+ eventOpts: {
+ category: 'Transactions',
+ action: 'Send Screen',
+ name: 'User clicks "Assets" dropdown',
+ },
+ customVariables: {
+ assetSelected: address ? 'ERC20' : 'ETH',
+ },
+ })
+ this.props.setSelectedToken(address)
+ })
+ }
+
+ render () {
+ const { t } = this.context
+
+ return (
+ <SendRowWrapper label={`${t('asset')}:`}>
+ <div className="send-v2__asset-dropdown">
+ { this.renderSelectedToken() }
+ { this.renderAssetDropdown() }
+ </div>
+ </SendRowWrapper>
+ )
+ }
+
+ renderSelectedToken () {
+ const { selectedTokenAddress } = this.props
+ const token = this.props.tokens.find(({ address }) => address === selectedTokenAddress)
+ return (
+ <div
+ className="send-v2__asset-dropdown__input-wrapper"
+ onClick={this.openDropdown}
+ >
+ { token ? this.renderAsset(token) : this.renderEth() }
+ </div>
+ )
+ }
+
+ renderAssetDropdown () {
+ return this.state.isShowingDropdown && (
+ <div>
+ <div
+ className="send-v2__asset-dropdown__close-area"
+ onClick={this.closeDropdown}
+ />
+ <div className="send-v2__asset-dropdown__list">
+ { this.renderEth() }
+ { this.props.tokens.map(token => this.renderAsset(token)) }
+ </div>
+ </div>
+ )
+ }
+
+ renderEth () {
+ const { t } = this.context
+ const { accounts, selectedAddress } = this.props
+
+ const balanceValue = accounts[selectedAddress] ? accounts[selectedAddress].balance : ''
+
+ return (
+ <div
+ className="send-v2__asset-dropdown__asset"
+ onClick={() => this.selectToken()}
+ >
+ <div className="send-v2__asset-dropdown__asset-icon">
+ <Identicon diameter={36} />
+ </div>
+ <div className="send-v2__asset-dropdown__asset-data">
+ <div className="send-v2__asset-dropdown__symbol">ETH</div>
+ <div className="send-v2__asset-dropdown__name">
+ <span className="send-v2__asset-dropdown__name__label">{`${t('balance')}:`}</span>
+ <UserPreferencedCurrencyDisplay
+ value={balanceValue}
+ type={PRIMARY}
+ />
+ </div>
+ </div>
+ </div>
+ )
+ }
+
+
+ renderAsset (token) {
+ const { address, symbol } = token
+ const { t } = this.context
+
+ return (
+ <div
+ key={address} className="send-v2__asset-dropdown__asset"
+ onClick={() => this.selectToken(address)}
+ >
+ <div className="send-v2__asset-dropdown__asset-icon">
+ <Identicon address={address} diameter={36} />
+ </div>
+ <div className="send-v2__asset-dropdown__asset-data">
+ <div className="send-v2__asset-dropdown__symbol">
+ { symbol }
+ </div>
+ <div className="send-v2__asset-dropdown__name">
+ <span className="send-v2__asset-dropdown__name__label">{`${t('balance')}:`}</span>
+ <TokenBalance
+ token={token}
+ withSymbol
+ />
+ </div>
+ </div>
+ </div>
+ )
+ }
+}
diff --git a/ui/app/pages/send/send-content/send-asset-row/send-asset-row.container.js b/ui/app/pages/send/send-content/send-asset-row/send-asset-row.container.js
new file mode 100644
index 000000000..57b62fba1
--- /dev/null
+++ b/ui/app/pages/send/send-content/send-asset-row/send-asset-row.container.js
@@ -0,0 +1,21 @@
+import { connect } from 'react-redux'
+import SendAssetRow from './send-asset-row.component'
+import {getMetaMaskAccounts} from '../../../../selectors/selectors'
+import { setSelectedToken } from '../../../../store/actions'
+
+function mapStateToProps (state) {
+ return {
+ tokens: state.metamask.tokens,
+ selectedAddress: state.metamask.selectedAddress,
+ selectedTokenAddress: state.metamask.selectedTokenAddress,
+ accounts: getMetaMaskAccounts(state),
+ }
+}
+
+function mapDispatchToProps (dispatch) {
+ return {
+ setSelectedToken: address => dispatch(setSelectedToken(address)),
+ }
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(SendAssetRow)
diff --git a/ui/app/components/app/send/send-content/send-content.component.js b/ui/app/pages/send/send-content/send-content.component.js
index 2c09ceb19..d799806c7 100644
--- a/ui/app/components/app/send/send-content/send-content.component.js
+++ b/ui/app/pages/send/send-content/send-content.component.js
@@ -1,11 +1,12 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
-import PageContainerContent from '../../../ui/page-container/page-container-content.component'
+import PageContainerContent from '../../../components/ui/page-container/page-container-content.component'
import SendAmountRow from './send-amount-row'
import SendFromRow from './send-from-row'
import SendGasRow from './send-gas-row'
import SendHexDataRow from './send-hex-data-row'
import SendToRow from './send-to-row'
+import SendAssetRow from './send-asset-row'
export default class SendContent extends Component {
@@ -26,6 +27,7 @@ export default class SendContent extends Component {
updateGas={this.updateGas}
scanQrCode={ _ => this.props.scanQrCode()}
/>
+ <SendAssetRow />
<SendAmountRow updateGas={this.updateGas} />
<SendGasRow />
{(this.props.showHexData && (
diff --git a/ui/app/components/app/send/send-content/send-dropdown-list/index.js b/ui/app/pages/send/send-content/send-dropdown-list/index.js
index 04af6536c..04af6536c 100644
--- a/ui/app/components/app/send/send-content/send-dropdown-list/index.js
+++ b/ui/app/pages/send/send-content/send-dropdown-list/index.js
diff --git a/ui/app/components/app/send/send-content/send-dropdown-list/send-dropdown-list.component.js b/ui/app/pages/send/send-content/send-dropdown-list/send-dropdown-list.component.js
index 0d026bc69..0d026bc69 100644
--- a/ui/app/components/app/send/send-content/send-dropdown-list/send-dropdown-list.component.js
+++ b/ui/app/pages/send/send-content/send-dropdown-list/send-dropdown-list.component.js
diff --git a/ui/app/components/app/send/send-content/send-dropdown-list/tests/send-dropdown-list-component.test.js b/ui/app/pages/send/send-content/send-dropdown-list/tests/send-dropdown-list-component.test.js
index b92dd4dfe..b92dd4dfe 100644
--- a/ui/app/components/app/send/send-content/send-dropdown-list/tests/send-dropdown-list-component.test.js
+++ b/ui/app/pages/send/send-content/send-dropdown-list/tests/send-dropdown-list-component.test.js
diff --git a/ui/app/components/app/send/send-content/send-from-row/index.js b/ui/app/pages/send/send-content/send-from-row/index.js
index 0a79726b2..0a79726b2 100644
--- a/ui/app/components/app/send/send-content/send-from-row/index.js
+++ b/ui/app/pages/send/send-content/send-from-row/index.js
diff --git a/ui/app/components/app/send/send-content/send-from-row/send-from-row.component.js b/ui/app/pages/send/send-content/send-from-row/send-from-row.component.js
index dfa53e970..dfa53e970 100644
--- a/ui/app/components/app/send/send-content/send-from-row/send-from-row.component.js
+++ b/ui/app/pages/send/send-content/send-from-row/send-from-row.component.js
diff --git a/ui/app/components/app/send/send-content/send-from-row/send-from-row.container.js b/ui/app/pages/send/send-content/send-from-row/send-from-row.container.js
index fe3ac9aa1..fe3ac9aa1 100644
--- a/ui/app/components/app/send/send-content/send-from-row/send-from-row.container.js
+++ b/ui/app/pages/send/send-content/send-from-row/send-from-row.container.js
diff --git a/ui/app/components/app/send/send-content/send-from-row/send-from-row.selectors.js b/ui/app/pages/send/send-content/send-from-row/send-from-row.selectors.js
index 03ef4806b..03ef4806b 100644
--- a/ui/app/components/app/send/send-content/send-from-row/send-from-row.selectors.js
+++ b/ui/app/pages/send/send-content/send-from-row/send-from-row.selectors.js
diff --git a/ui/app/components/app/send/send-content/send-from-row/tests/send-from-row-component.test.js b/ui/app/pages/send/send-content/send-from-row/tests/send-from-row-component.test.js
index 18811c57e..18811c57e 100644
--- a/ui/app/components/app/send/send-content/send-from-row/tests/send-from-row-component.test.js
+++ b/ui/app/pages/send/send-content/send-from-row/tests/send-from-row-component.test.js
diff --git a/ui/app/components/app/send/send-content/send-from-row/tests/send-from-row-container.test.js b/ui/app/pages/send/send-content/send-from-row/tests/send-from-row-container.test.js
index fd771ea77..fd771ea77 100644
--- a/ui/app/components/app/send/send-content/send-from-row/tests/send-from-row-container.test.js
+++ b/ui/app/pages/send/send-content/send-from-row/tests/send-from-row-container.test.js
diff --git a/ui/app/components/app/send/send-content/send-from-row/tests/send-from-row-selectors.test.js b/ui/app/pages/send/send-content/send-from-row/tests/send-from-row-selectors.test.js
index ecb57bbc3..ecb57bbc3 100644
--- a/ui/app/components/app/send/send-content/send-from-row/tests/send-from-row-selectors.test.js
+++ b/ui/app/pages/send/send-content/send-from-row/tests/send-from-row-selectors.test.js
diff --git a/ui/app/components/app/send/send-content/send-gas-row/README.md b/ui/app/pages/send/send-content/send-gas-row/README.md
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/app/send/send-content/send-gas-row/README.md
+++ b/ui/app/pages/send/send-content/send-gas-row/README.md
diff --git a/ui/app/components/app/send/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js b/ui/app/pages/send/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js
index 48088607a..3f5587318 100644
--- a/ui/app/components/app/send/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js
+++ b/ui/app/pages/send/send-content/send-gas-row/gas-fee-display/gas-fee-display.component.js
@@ -1,7 +1,7 @@
import React, {Component} from 'react'
import PropTypes from 'prop-types'
-import UserPreferencedCurrencyDisplay from '../../../../user-preferenced-currency-display'
-import { PRIMARY, SECONDARY } from '../../../../../../helpers/constants/common'
+import UserPreferencedCurrencyDisplay from '../../../../../components/app/user-preferenced-currency-display'
+import { PRIMARY, SECONDARY } from '../../../../../helpers/constants/common'
export default class GasFeeDisplay extends Component {
diff --git a/ui/app/components/app/send/send-content/send-gas-row/gas-fee-display/index.js b/ui/app/pages/send/send-content/send-gas-row/gas-fee-display/index.js
index dba0edb7b..dba0edb7b 100644
--- a/ui/app/components/app/send/send-content/send-gas-row/gas-fee-display/index.js
+++ b/ui/app/pages/send/send-content/send-gas-row/gas-fee-display/index.js
diff --git a/ui/app/components/app/send/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js b/ui/app/pages/send/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js
index cb4180508..eedd43221 100644
--- a/ui/app/components/app/send/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js
+++ b/ui/app/pages/send/send-content/send-gas-row/gas-fee-display/test/gas-fee-display.component.test.js
@@ -2,7 +2,7 @@ import React from 'react'
import assert from 'assert'
import {shallow} from 'enzyme'
import GasFeeDisplay from '../gas-fee-display.component'
-import UserPreferencedCurrencyDisplay from '../../../../../user-preferenced-currency-display'
+import UserPreferencedCurrencyDisplay from '../../../../../../components/app/user-preferenced-currency-display'
import sinon from 'sinon'
diff --git a/ui/app/components/app/send/send-content/send-gas-row/index.js b/ui/app/pages/send/send-content/send-gas-row/index.js
index 3c7ff1d5f..3c7ff1d5f 100644
--- a/ui/app/components/app/send/send-content/send-gas-row/index.js
+++ b/ui/app/pages/send/send-content/send-gas-row/index.js
diff --git a/ui/app/components/app/send/send-content/send-gas-row/send-gas-row.component.js b/ui/app/pages/send/send-content/send-gas-row/send-gas-row.component.js
index 424a65b20..4c09ed564 100644
--- a/ui/app/components/app/send/send-content/send-gas-row/send-gas-row.component.js
+++ b/ui/app/pages/send/send-content/send-gas-row/send-gas-row.component.js
@@ -2,26 +2,31 @@ import React, { Component } from 'react'
import PropTypes from 'prop-types'
import SendRowWrapper from '../send-row-wrapper'
import GasFeeDisplay from './gas-fee-display/gas-fee-display.component'
-import GasPriceButtonGroup from '../../../gas-customization/gas-price-button-group'
-import AdvancedGasInputs from '../../../gas-customization/advanced-gas-inputs'
+import GasPriceButtonGroup from '../../../../components/app/gas-customization/gas-price-button-group'
+import AdvancedGasInputs from '../../../../components/app/gas-customization/advanced-gas-inputs'
export default class SendGasRow extends Component {
static propTypes = {
+ balance: PropTypes.string,
conversionRate: PropTypes.number,
convertedCurrency: PropTypes.string,
gasFeeError: PropTypes.bool,
gasLoadingError: PropTypes.bool,
gasTotal: PropTypes.string,
+ maxModeOn: PropTypes.bool,
showCustomizeGasModal: PropTypes.func,
+ selectedToken: PropTypes.object,
+ setAmountToMax: PropTypes.func,
setGasPrice: PropTypes.func,
setGasLimit: PropTypes.func,
+ tokenBalance: PropTypes.string,
gasPriceButtonGroupProps: PropTypes.object,
gasButtonGroupShown: PropTypes.bool,
advancedInlineGasShown: PropTypes.bool,
resetGasButtons: PropTypes.func,
- gasPrice: PropTypes.number,
- gasLimit: PropTypes.number,
+ gasPrice: PropTypes.string,
+ gasLimit: PropTypes.string,
insufficientBalance: PropTypes.bool,
}
@@ -47,6 +52,23 @@ export default class SendGasRow extends Component {
</div>
}
+ setMaxAmount () {
+ const {
+ balance,
+ gasTotal,
+ selectedToken,
+ setAmountToMax,
+ tokenBalance,
+ } = this.props
+
+ setAmountToMax({
+ balance,
+ gasTotal,
+ selectedToken,
+ tokenBalance,
+ })
+ }
+
renderContent () {
const {
conversionRate,
@@ -57,6 +79,7 @@ export default class SendGasRow extends Component {
gasPriceButtonGroupProps,
gasButtonGroupShown,
advancedInlineGasShown,
+ maxModeOn,
resetGasButtons,
setGasPrice,
setGasLimit,
@@ -71,7 +94,7 @@ export default class SendGasRow extends Component {
className="gas-price-button-group--small"
showCheck={false}
{...gasPriceButtonGroupProps}
- handleGasPriceSelection={(...args) => {
+ handleGasPriceSelection={async (...args) => {
metricsEvent({
eventOpts: {
category: 'Transactions',
@@ -79,7 +102,10 @@ export default class SendGasRow extends Component {
name: 'Changed Gas Button',
},
})
- gasPriceButtonGroupProps.handleGasPriceSelection(...args)
+ await gasPriceButtonGroupProps.handleGasPriceSelection(...args)
+ if (maxModeOn) {
+ this.setMaxAmount()
+ }
}}
/>
{ this.renderAdvancedOptionsButton() }
@@ -89,7 +115,12 @@ export default class SendGasRow extends Component {
convertedCurrency={convertedCurrency}
gasLoadingError={gasLoadingError}
gasTotal={gasTotal}
- onReset={resetGasButtons}
+ onReset={() => {
+ resetGasButtons()
+ if (maxModeOn) {
+ this.setMaxAmount()
+ }
+ }}
onClick={() => showCustomizeGasModal()}
/>
const advancedGasInputs = <div>
diff --git a/ui/app/components/app/send/send-content/send-gas-row/send-gas-row.container.js b/ui/app/pages/send/send-content/send-gas-row/send-gas-row.container.js
index f81670c02..10eaa50b8 100644
--- a/ui/app/components/app/send/send-content/send-gas-row/send-gas-row.container.js
+++ b/ui/app/pages/send/send-content/send-gas-row/send-gas-row.container.js
@@ -6,29 +6,37 @@ import {
getGasPrice,
getGasLimit,
getSendAmount,
+ getSendFromBalance,
+ getTokenBalance,
} from '../../send.selectors.js'
import {
+ getMaxModeOn,
+} from '../send-amount-row/amount-max-button/amount-max-button.selectors'
+import {
isBalanceSufficient,
calcGasTotal,
} from '../../send.utils.js'
+import { calcMaxAmount } from '../send-amount-row/amount-max-button/amount-max-button.utils'
import {
getBasicGasEstimateLoadingStatus,
getRenderableEstimateDataForSmallButtonsFromGWEI,
getDefaultActiveButtonIndex,
-} from '../../../../../selectors/custom-gas'
+} from '../../../../selectors/custom-gas'
import {
showGasButtonGroup,
-} from '../../../../../ducks/send/send.duck'
+ updateSendErrors,
+} from '../../../../ducks/send/send.duck'
import {
resetCustomData,
setCustomGasPrice,
setCustomGasLimit,
-} from '../../../../../ducks/gas/gas.duck'
+} from '../../../../ducks/gas/gas.duck'
import { getGasLoadingError, gasFeeIsInError, getGasButtonGroupShown } from './send-gas-row.selectors.js'
-import { showModal, setGasPrice, setGasLimit, setGasTotal } from '../../../../../store/actions'
-import { getAdvancedInlineGasShown, getCurrentEthBalance, getSelectedToken } from '../../../../../selectors/selectors'
+import { showModal, setGasPrice, setGasLimit, setGasTotal, updateSendAmount } from '../../../../store/actions'
+import { getAdvancedInlineGasShown, getCurrentEthBalance, getSelectedToken } from '../../../../selectors/selectors'
import SendGasRow from './send-gas-row.component'
+
export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(SendGasRow)
function mapStateToProps (state) {
@@ -49,6 +57,7 @@ function mapStateToProps (state) {
})
return {
+ balance: getSendFromBalance(state),
conversionRate,
convertedCurrency: getCurrentCurrency(state),
gasTotal,
@@ -65,6 +74,9 @@ function mapStateToProps (state) {
gasPrice,
gasLimit,
insufficientBalance,
+ maxModeOn: getMaxModeOn(state),
+ selectedToken: getSelectedToken(state),
+ tokenBalance: getTokenBalance(state),
}
}
@@ -85,6 +97,10 @@ function mapDispatchToProps (dispatch) {
dispatch(setGasTotal(calcGasTotal(newLimit, gasPrice)))
}
},
+ setAmountToMax: maxAmountDataObject => {
+ dispatch(updateSendErrors({ amount: null }))
+ dispatch(updateSendAmount(calcMaxAmount(maxAmountDataObject)))
+ },
showGasButtonGroup: () => dispatch(showGasButtonGroup()),
resetCustomData: () => dispatch(resetCustomData()),
}
diff --git a/ui/app/components/app/send/send-content/send-gas-row/send-gas-row.scss b/ui/app/pages/send/send-content/send-gas-row/send-gas-row.scss
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/app/send/send-content/send-gas-row/send-gas-row.scss
+++ b/ui/app/pages/send/send-content/send-gas-row/send-gas-row.scss
diff --git a/ui/app/components/app/send/send-content/send-gas-row/send-gas-row.selectors.js b/ui/app/pages/send/send-content/send-gas-row/send-gas-row.selectors.js
index 79c838543..79c838543 100644
--- a/ui/app/components/app/send/send-content/send-gas-row/send-gas-row.selectors.js
+++ b/ui/app/pages/send/send-content/send-gas-row/send-gas-row.selectors.js
diff --git a/ui/app/components/app/send/send-content/send-gas-row/tests/send-gas-row-component.test.js b/ui/app/pages/send/send-content/send-gas-row/tests/send-gas-row-component.test.js
index 08f26854e..0cbc92621 100644
--- a/ui/app/components/app/send/send-content/send-gas-row/tests/send-gas-row-component.test.js
+++ b/ui/app/pages/send/send-content/send-gas-row/tests/send-gas-row-component.test.js
@@ -6,7 +6,7 @@ import SendGasRow from '../send-gas-row.component.js'
import SendRowWrapper from '../../send-row-wrapper/send-row-wrapper.component'
import GasFeeDisplay from '../gas-fee-display/gas-fee-display.component'
-import GasPriceButtonGroup from '../../../../gas-customization/gas-price-button-group'
+import GasPriceButtonGroup from '../../../../../components/app/gas-customization/gas-price-button-group'
const propsMethodSpies = {
showCustomizeGasModal: sinon.spy(),
diff --git a/ui/app/components/app/send/send-content/send-gas-row/tests/send-gas-row-container.test.js b/ui/app/pages/send/send-content/send-gas-row/tests/send-gas-row-container.test.js
index d1f753639..4acb310f8 100644
--- a/ui/app/components/app/send/send-content/send-gas-row/tests/send-gas-row-container.test.js
+++ b/ui/app/pages/send/send-content/send-gas-row/tests/send-gas-row-container.test.js
@@ -32,7 +32,7 @@ proxyquire('../send-gas-row.container.js', {
return () => ({})
},
},
- '../../../../../selectors/selectors': {
+ '../../../../selectors/selectors': {
getCurrentEthBalance: (s) => `mockCurrentEthBalance:${s}`,
getAdvancedInlineGasShown: (s) => `mockAdvancedInlineGasShown:${s}`,
getSelectedToken: () => false,
@@ -44,6 +44,11 @@ proxyquire('../send-gas-row.container.js', {
getGasPrice: (s) => `mockGasPrice:${s}`,
getGasLimit: (s) => `mockGasLimit:${s}`,
getSendAmount: (s) => `mockSendAmount:${s}`,
+ getSendFromBalance: (s) => `mockBalance:${s}`,
+ getTokenBalance: (s) => `mockTokenBalance:${s}`,
+ },
+ '../send-amount-row/amount-max-button/amount-max-button.selectors': {
+ getMaxModeOn: (s) => `mockMaxModeOn:${s}`,
},
'../../send.utils.js': {
isBalanceSufficient: ({
@@ -59,14 +64,14 @@ proxyquire('../send-gas-row.container.js', {
gasFeeIsInError: (s) => `mockGasFeeError:${s}`,
getGasButtonGroupShown: (s) => `mockGetGasButtonGroupShown:${s}`,
},
- '../../../../../store/actions': actionSpies,
- '../../../../../selectors/custom-gas': {
+ '../../../../store/actions': actionSpies,
+ '../../../../selectors/custom-gas': {
getBasicGasEstimateLoadingStatus: (s) => `mockBasicGasEstimateLoadingStatus:${s}`,
getRenderableEstimateDataForSmallButtonsFromGWEI: (s) => `mockGasButtonInfo:${s}`,
getDefaultActiveButtonIndex: (gasButtonInfo, gasPrice) => gasButtonInfo.length + gasPrice.length,
},
- '../../../../../ducks/send/send.duck': sendDuckSpies,
- '../../../../../ducks/gas/gas.duck': gasDuckSpies,
+ '../../../../ducks/send/send.duck': sendDuckSpies,
+ '../../../../ducks/gas/gas.duck': gasDuckSpies,
})
describe('send-gas-row container', () => {
@@ -75,6 +80,7 @@ describe('send-gas-row container', () => {
it('should map the correct properties to props', () => {
assert.deepEqual(mapStateToProps('mockState'), {
+ balance: 'mockBalance:mockState',
conversionRate: 'mockConversionRate:mockState',
convertedCurrency: 'mockConvertedCurrency:mockState',
gasTotal: 'mockGasTotal:mockState',
@@ -91,6 +97,9 @@ describe('send-gas-row container', () => {
gasLimit: 'mockGasLimit:mockState',
gasPrice: 'mockGasPrice:mockState',
insufficientBalance: false,
+ maxModeOn: 'mockMaxModeOn:mockState',
+ selectedToken: false,
+ tokenBalance: 'mockTokenBalance:mockState',
})
})
diff --git a/ui/app/components/app/send/send-content/send-gas-row/tests/send-gas-row-selectors.test.js b/ui/app/pages/send/send-content/send-gas-row/tests/send-gas-row-selectors.test.js
index bd3c9a257..bd3c9a257 100644
--- a/ui/app/components/app/send/send-content/send-gas-row/tests/send-gas-row-selectors.test.js
+++ b/ui/app/pages/send/send-content/send-gas-row/tests/send-gas-row-selectors.test.js
diff --git a/ui/app/components/app/send/send-content/send-hex-data-row/index.js b/ui/app/pages/send/send-content/send-hex-data-row/index.js
index 08c341067..08c341067 100644
--- a/ui/app/components/app/send/send-content/send-hex-data-row/index.js
+++ b/ui/app/pages/send/send-content/send-hex-data-row/index.js
diff --git a/ui/app/components/app/send/send-content/send-hex-data-row/send-hex-data-row.component.js b/ui/app/pages/send/send-content/send-hex-data-row/send-hex-data-row.component.js
index 62a74a77b..62a74a77b 100644
--- a/ui/app/components/app/send/send-content/send-hex-data-row/send-hex-data-row.component.js
+++ b/ui/app/pages/send/send-content/send-hex-data-row/send-hex-data-row.component.js
diff --git a/ui/app/components/app/send/send-content/send-hex-data-row/send-hex-data-row.container.js b/ui/app/pages/send/send-content/send-hex-data-row/send-hex-data-row.container.js
index 76c929d08..8b1c540c3 100644
--- a/ui/app/components/app/send/send-content/send-hex-data-row/send-hex-data-row.container.js
+++ b/ui/app/pages/send/send-content/send-hex-data-row/send-hex-data-row.container.js
@@ -1,7 +1,7 @@
import { connect } from 'react-redux'
import {
updateSendHexData,
-} from '../../../../../store/actions'
+} from '../../../../store/actions'
import SendHexDataRow from './send-hex-data-row.component'
export default connect(mapStateToProps, mapDispatchToProps)(SendHexDataRow)
diff --git a/ui/app/components/app/send/send-content/send-row-wrapper/index.js b/ui/app/pages/send/send-content/send-row-wrapper/index.js
index d17545dcc..d17545dcc 100644
--- a/ui/app/components/app/send/send-content/send-row-wrapper/index.js
+++ b/ui/app/pages/send/send-content/send-row-wrapper/index.js
diff --git a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/index.js b/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/index.js
index c00617f83..c00617f83 100644
--- a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/index.js
+++ b/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/index.js
diff --git a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message-README.md b/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message-README.md
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message-README.md
+++ b/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message-README.md
diff --git a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js b/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js
index 61bc7bab7..0be01996a 100644
--- a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js
+++ b/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.component.js
@@ -1,5 +1,6 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
+import classnames from 'classnames'
export default class SendRowErrorMessage extends Component {
@@ -19,7 +20,7 @@ export default class SendRowErrorMessage extends Component {
return (
errorMessage
- ? <div className="send-v2__error">{this.context.t(errorMessage)}</div>
+ ? <div className={classnames('send-v2__error', {'send-v2__error-amount': errorType === 'amount'})}>{this.context.t(errorMessage)}</div>
: null
)
}
diff --git a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js b/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js
index 59622047f..59622047f 100644
--- a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js
+++ b/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.container.js
diff --git a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.scss b/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.scss
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.scss
+++ b/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/send-row-error-message.scss
diff --git a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-component.test.js b/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-component.test.js
index 2304a43d2..2304a43d2 100644
--- a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-component.test.js
+++ b/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-component.test.js
diff --git a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-container.test.js b/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-container.test.js
index eecff165d..2013e3200 100644
--- a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-container.test.js
+++ b/ui/app/pages/send/send-content/send-row-wrapper/send-row-error-message/tests/send-row-error-message-container.test.js
@@ -5,7 +5,7 @@ let mapStateToProps
proxyquire('../send-row-error-message.container.js', {
'react-redux': {
- connect: (ms, md) => {
+ connect: (ms) => {
mapStateToProps = ms
return () => ({})
},
diff --git a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/index.js b/ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/index.js
index fd4d19ef7..fd4d19ef7 100644
--- a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/index.js
+++ b/ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/index.js
diff --git a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.component.js b/ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.component.js
index f1caa8f99..f1caa8f99 100644
--- a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.component.js
+++ b/ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.component.js
diff --git a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.container.js b/ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.container.js
index 7df14fd96..7df14fd96 100644
--- a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.container.js
+++ b/ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.container.js
diff --git a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.scss b/ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.scss
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.scss
+++ b/ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/send-row-warning-message.scss
diff --git a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-component.test.js b/ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-component.test.js
index bd803d833..bd803d833 100644
--- a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-component.test.js
+++ b/ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-component.test.js
diff --git a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-container.test.js b/ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-container.test.js
index 225bf056c..6c0739f0e 100644
--- a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-container.test.js
+++ b/ui/app/pages/send/send-content/send-row-wrapper/send-row-warning-message/tests/send-row-warning-message-container.test.js
@@ -5,7 +5,7 @@ let mapStateToProps
proxyquire('../send-row-warning-message.container.js', {
'react-redux': {
- connect: (ms, md) => {
+ connect: (ms) => {
mapStateToProps = ms
return () => ({})
},
diff --git a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-wrapper-README.md b/ui/app/pages/send/send-content/send-row-wrapper/send-row-wrapper-README.md
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-wrapper-README.md
+++ b/ui/app/pages/send/send-content/send-row-wrapper/send-row-wrapper-README.md
diff --git a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-wrapper.component.js b/ui/app/pages/send/send-content/send-row-wrapper/send-row-wrapper.component.js
index 94309bd96..075b86633 100644
--- a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-wrapper.component.js
+++ b/ui/app/pages/send/send-content/send-row-wrapper/send-row-wrapper.component.js
@@ -18,7 +18,7 @@ export default class SendRowWrapper extends Component {
t: PropTypes.func,
};
- render () {
+ renderAmountFormRow () {
const {
children,
errorType = '',
@@ -34,7 +34,39 @@ export default class SendRowWrapper extends Component {
<div className="send-v2__form-row">
<div className="send-v2__form-label">
{label}
- {showError && <SendRowErrorMessage errorType={errorType}/>}
+ {customLabelContent}
+ </div>
+ <div className="send-v2__form-field-container">
+ <div className="send-v2__form-field">
+ {formField}
+ </div>
+ <div>
+ {showError && <SendRowErrorMessage errorType={errorType} />}
+ {!showError && showWarning && <SendRowWarningMessage warningType={warningType} />}
+ </div>
+ </div>
+ </div>
+ )
+ }
+
+ renderFormRow () {
+ const {
+ children,
+ errorType = '',
+ label,
+ showError = false,
+ showWarning = false,
+ warningType = '',
+ } = this.props
+
+ const formField = Array.isArray(children) ? children[1] || children[0] : children
+ const customLabelContent = (Array.isArray(children) && children.length) > 1 ? children[0] : null
+
+ return (
+ <div className="send-v2__form-row">
+ <div className="send-v2__form-label">
+ {label}
+ {showError && <SendRowErrorMessage errorType={errorType} />}
{!showError && showWarning && <SendRowWarningMessage warningType={warningType} />}
{customLabelContent}
</div>
@@ -45,4 +77,14 @@ export default class SendRowWrapper extends Component {
)
}
+ render () {
+ const {
+ errorType = '',
+ } = this.props
+
+ return (
+ errorType === 'amount' ? this.renderAmountFormRow() : this.renderFormRow()
+ )
+ }
+
}
diff --git a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-wrapper.scss b/ui/app/pages/send/send-content/send-row-wrapper/send-row-wrapper.scss
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/app/send/send-content/send-row-wrapper/send-row-wrapper.scss
+++ b/ui/app/pages/send/send-content/send-row-wrapper/send-row-wrapper.scss
diff --git a/ui/app/components/app/send/send-content/send-row-wrapper/tests/send-row-wrapper-component.test.js b/ui/app/pages/send/send-content/send-row-wrapper/tests/send-row-wrapper-component.test.js
index 30280e1d0..30280e1d0 100644
--- a/ui/app/components/app/send/send-content/send-row-wrapper/tests/send-row-wrapper-component.test.js
+++ b/ui/app/pages/send/send-content/send-row-wrapper/tests/send-row-wrapper-component.test.js
diff --git a/ui/app/components/app/send/send-content/send-to-row/index.js b/ui/app/pages/send/send-content/send-to-row/index.js
index 121f15148..121f15148 100644
--- a/ui/app/components/app/send/send-content/send-to-row/index.js
+++ b/ui/app/pages/send/send-content/send-to-row/index.js
diff --git a/ui/app/components/app/send/send-content/send-to-row/send-to-row-README.md b/ui/app/pages/send/send-content/send-to-row/send-to-row-README.md
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/app/send/send-content/send-to-row/send-to-row-README.md
+++ b/ui/app/pages/send/send-content/send-to-row/send-to-row-README.md
diff --git a/ui/app/components/app/send/send-content/send-to-row/send-to-row.component.js b/ui/app/pages/send/send-content/send-to-row/send-to-row.component.js
index e8a55cb2a..9baf327c1 100644
--- a/ui/app/components/app/send/send-content/send-to-row/send-to-row.component.js
+++ b/ui/app/pages/send/send-content/send-to-row/send-to-row.component.js
@@ -1,7 +1,7 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import SendRowWrapper from '../send-row-wrapper'
-import EnsInput from '../../../ens-input'
+import EnsInput from '../../../../components/app/ens-input'
import { getToErrorObject, getToWarningObject } from './send-to-row.utils.js'
export default class SendToRow extends Component {
diff --git a/ui/app/components/app/send/send-content/send-to-row/send-to-row.container.js b/ui/app/pages/send/send-content/send-to-row/send-to-row.container.js
index 30865d295..2cbe9fcd0 100644
--- a/ui/app/components/app/send/send-content/send-to-row/send-to-row.container.js
+++ b/ui/app/pages/send/send-content/send-to-row/send-to-row.container.js
@@ -14,13 +14,13 @@ import {
} from './send-to-row.selectors.js'
import {
updateSendTo,
-} from '../../../../../store/actions'
+} from '../../../../store/actions'
import {
updateSendErrors,
updateSendWarnings,
openToDropdown,
closeToDropdown,
-} from '../../../../../ducks/send/send.duck'
+} from '../../../../ducks/send/send.duck'
import SendToRow from './send-to-row.component'
export default connect(mapStateToProps, mapDispatchToProps)(SendToRow)
diff --git a/ui/app/components/app/send/send-content/send-to-row/send-to-row.selectors.js b/ui/app/pages/send/send-content/send-to-row/send-to-row.selectors.js
index a6160d335..a6160d335 100644
--- a/ui/app/components/app/send/send-content/send-to-row/send-to-row.selectors.js
+++ b/ui/app/pages/send/send-content/send-to-row/send-to-row.selectors.js
diff --git a/ui/app/components/app/send/send-content/send-to-row/send-to-row.utils.js b/ui/app/pages/send/send-content/send-to-row/send-to-row.utils.js
index 60e75d34c..b3b0d2da3 100644
--- a/ui/app/components/app/send/send-content/send-to-row/send-to-row.utils.js
+++ b/ui/app/pages/send/send-content/send-to-row/send-to-row.utils.js
@@ -4,22 +4,21 @@ const {
KNOWN_RECIPIENT_ADDRESS_ERROR,
INVALID_RECIPIENT_ADDRESS_NOT_ETH_NETWORK_ERROR,
} = require('../../send.constants')
-const { isValidAddress, isEthNetwork } = require('../../../../../helpers/utils/util')
-import { checkExistingAddresses } from '../../../../../pages/add-token/util'
+const { isValidAddress, isEthNetwork } = require('../../../../helpers/utils/util')
+import { checkExistingAddresses } from '../../../add-token/util'
const ethUtil = require('ethereumjs-util')
const contractMap = require('eth-contract-metadata')
-function getToErrorObject (to, toError = null, hasHexData = false, tokens = [], selectedToken = null, network) {
+function getToErrorObject (to, toError = null, hasHexData = false, _, __, network) {
if (!to) {
if (!hasHexData) {
toError = REQUIRED_ERROR
}
} else if (!isValidAddress(to, network) && !toError) {
toError = isEthNetwork(network) ? INVALID_RECIPIENT_ADDRESS_ERROR : INVALID_RECIPIENT_ADDRESS_NOT_ETH_NETWORK_ERROR
- } else if (selectedToken && (ethUtil.toChecksumAddress(to) in contractMap || checkExistingAddresses(to, tokens))) {
- toError = KNOWN_RECIPIENT_ADDRESS_ERROR
}
+
return { to: toError }
}
diff --git a/ui/app/components/app/send/send-content/send-to-row/tests/send-to-row-component.test.js b/ui/app/pages/send/send-content/send-to-row/tests/send-to-row-component.test.js
index d4d054057..c180d97f1 100644
--- a/ui/app/components/app/send/send-content/send-to-row/tests/send-to-row-component.test.js
+++ b/ui/app/pages/send/send-content/send-to-row/tests/send-to-row-component.test.js
@@ -16,7 +16,7 @@ const SendToRow = proxyquire('../send-to-row.component.js', {
}).default
import SendRowWrapper from '../../send-row-wrapper/send-row-wrapper.component'
-import EnsInput from '../../../../ens-input'
+import EnsInput from '../../../../../components/app/ens-input'
const propsMethodSpies = {
closeToDropdown: sinon.spy(),
diff --git a/ui/app/components/app/send/send-content/send-to-row/tests/send-to-row-container.test.js b/ui/app/pages/send/send-content/send-to-row/tests/send-to-row-container.test.js
index 94b4f1024..bb8702e9a 100644
--- a/ui/app/components/app/send/send-content/send-to-row/tests/send-to-row-container.test.js
+++ b/ui/app/pages/send/send-content/send-to-row/tests/send-to-row-container.test.js
@@ -36,8 +36,8 @@ proxyquire('../send-to-row.container.js', {
sendToIsInWarning: (s) => `mockInWarning:${s}`,
getTokens: (s) => `mockTokens:${s}`,
},
- '../../../../../store/actions': actionSpies,
- '../../../../../ducks/send/send.duck': duckActionSpies,
+ '../../../../store/actions': actionSpies,
+ '../../../../ducks/send/send.duck': duckActionSpies,
})
describe('send-to-row container', () => {
diff --git a/ui/app/components/app/send/send-content/send-to-row/tests/send-to-row-selectors.test.js b/ui/app/pages/send/send-content/send-to-row/tests/send-to-row-selectors.test.js
index 0fa342d1e..0fa342d1e 100644
--- a/ui/app/components/app/send/send-content/send-to-row/tests/send-to-row-selectors.test.js
+++ b/ui/app/pages/send/send-content/send-to-row/tests/send-to-row-selectors.test.js
diff --git a/ui/app/components/app/send/send-content/send-to-row/tests/send-to-row-utils.test.js b/ui/app/pages/send/send-content/send-to-row/tests/send-to-row-utils.test.js
index 95882d640..f8a6dd96f 100644
--- a/ui/app/components/app/send/send-content/send-to-row/tests/send-to-row-utils.test.js
+++ b/ui/app/pages/send/send-content/send-to-row/tests/send-to-row-utils.test.js
@@ -13,7 +13,7 @@ const stubs = {
}
const toRowUtils = proxyquire('../send-to-row.utils.js', {
- '../../../../../helpers/utils/util': {
+ '../../../../helpers/utils/util': {
isValidAddress: stubs.isValidAddress,
},
})
@@ -55,9 +55,9 @@ describe('send-to-row utils', () => {
})
})
- it('should return a known address recipient if to is truthy but part of state tokens', () => {
+ it('should return null if to is truthy but part of state tokens', () => {
assert.deepEqual(getToErrorObject('0xabc123', undefined, false, [{'address': '0xabc123'}], {'address': '0xabc123'}), {
- to: KNOWN_RECIPIENT_ADDRESS_ERROR,
+ to: null,
})
})
@@ -67,14 +67,14 @@ describe('send-to-row utils', () => {
})
})
- it('should return a known address recipient if to is truthy but part of contract metadata', () => {
+ it('should return null if to is truthy but part of contract metadata', () => {
assert.deepEqual(getToErrorObject('0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', undefined, false, [{'address': '0xabc123'}], {'address': '0xabc123'}), {
- to: KNOWN_RECIPIENT_ADDRESS_ERROR,
+ to: null,
})
})
it('should null if to is truthy part of contract metadata but selectedToken falsy', () => {
assert.deepEqual(getToErrorObject('0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359', undefined, false, [{'address': '0xabc123'}], {'address': '0xabc123'}), {
- to: KNOWN_RECIPIENT_ADDRESS_ERROR,
+ to: null,
})
})
})
diff --git a/ui/app/components/app/send/send-content/tests/send-content-component.test.js b/ui/app/pages/send/send-content/tests/send-content-component.test.js
index 7d102c930..521c6523e 100644
--- a/ui/app/components/app/send/send-content/tests/send-content-component.test.js
+++ b/ui/app/pages/send/send-content/tests/send-content-component.test.js
@@ -3,12 +3,13 @@ import assert from 'assert'
import { shallow } from 'enzyme'
import SendContent from '../send-content.component.js'
-import PageContainerContent from '../../../../ui/page-container/page-container-content.component'
+import PageContainerContent from '../../../../components/ui/page-container/page-container-content.component'
import SendAmountRow from '../send-amount-row/send-amount-row.container'
import SendFromRow from '../send-from-row/send-from-row.container'
import SendGasRow from '../send-gas-row/send-gas-row.container'
import SendToRow from '../send-to-row/send-to-row.container'
import SendHexDataRow from '../send-hex-data-row/send-hex-data-row.container'
+import SendAssetRow from '../send-asset-row/send-asset-row.container'
describe('SendContent Component', function () {
let wrapper
@@ -32,9 +33,10 @@ describe('SendContent Component', function () {
const PageContainerContentChild = wrapper.find(PageContainerContent).children()
assert(PageContainerContentChild.childAt(0).is(SendFromRow))
assert(PageContainerContentChild.childAt(1).is(SendToRow))
- assert(PageContainerContentChild.childAt(2).is(SendAmountRow))
- assert(PageContainerContentChild.childAt(3).is(SendGasRow))
- assert(PageContainerContentChild.childAt(4).is(SendHexDataRow))
+ assert(PageContainerContentChild.childAt(2).is(SendAssetRow))
+ assert(PageContainerContentChild.childAt(3).is(SendAmountRow))
+ assert(PageContainerContentChild.childAt(4).is(SendGasRow))
+ assert(PageContainerContentChild.childAt(5).is(SendHexDataRow))
})
it('should not render the SendHexDataRow if props.showHexData is false', () => {
@@ -42,9 +44,10 @@ describe('SendContent Component', function () {
const PageContainerContentChild = wrapper.find(PageContainerContent).children()
assert(PageContainerContentChild.childAt(0).is(SendFromRow))
assert(PageContainerContentChild.childAt(1).is(SendToRow))
- assert(PageContainerContentChild.childAt(2).is(SendAmountRow))
- assert(PageContainerContentChild.childAt(3).is(SendGasRow))
- assert.equal(PageContainerContentChild.childAt(4).exists(), false)
+ assert(PageContainerContentChild.childAt(2).is(SendAssetRow))
+ assert(PageContainerContentChild.childAt(3).is(SendAmountRow))
+ assert(PageContainerContentChild.childAt(4).is(SendGasRow))
+ assert.equal(PageContainerContentChild.childAt(5).exists(), false)
})
})
})
diff --git a/ui/app/components/app/send/send-footer/README.md b/ui/app/pages/send/send-footer/README.md
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/app/send/send-footer/README.md
+++ b/ui/app/pages/send/send-footer/README.md
diff --git a/ui/app/components/app/send/send-footer/index.js b/ui/app/pages/send/send-footer/index.js
index 58e91d622..58e91d622 100644
--- a/ui/app/components/app/send/send-footer/index.js
+++ b/ui/app/pages/send/send-footer/index.js
diff --git a/ui/app/components/app/send/send-footer/send-footer.component.js b/ui/app/pages/send/send-footer/send-footer.component.js
index cc891a9b3..16a8fdde2 100644
--- a/ui/app/components/app/send/send-footer/send-footer.component.js
+++ b/ui/app/pages/send/send-footer/send-footer.component.js
@@ -1,7 +1,7 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
-import PageContainerFooter from '../../../ui/page-container/page-container-footer'
-import { CONFIRM_TRANSACTION_ROUTE, DEFAULT_ROUTE } from '../../../../helpers/constants/routes'
+import PageContainerFooter from '../../../components/ui/page-container/page-container-footer'
+import { CONFIRM_TRANSACTION_ROUTE, DEFAULT_ROUTE } from '../../../helpers/constants/routes'
export default class SendFooter extends Component {
@@ -27,6 +27,7 @@ export default class SendFooter extends Component {
unapprovedTxs: PropTypes.object,
update: PropTypes.func,
sendErrors: PropTypes.object,
+ gasChangedLabel: PropTypes.string,
}
static contextTypes = {
@@ -57,6 +58,7 @@ export default class SendFooter extends Component {
update,
toAccounts,
history,
+ gasChangedLabel,
} = this.props
const { metricsEvent } = this.context
@@ -91,6 +93,9 @@ export default class SendFooter extends Component {
action: 'Edit Screen',
name: 'Complete',
},
+ customVariables: {
+ gasChanged: gasChangedLabel,
+ },
})
history.push(CONFIRM_TRANSACTION_ROUTE)
})
diff --git a/ui/app/components/app/send/send-footer/send-footer.container.js b/ui/app/pages/send/send-footer/send-footer.container.js
index ea3fd7ee4..68f4dc7c3 100644
--- a/ui/app/components/app/send/send-footer/send-footer.container.js
+++ b/ui/app/pages/send/send-footer/send-footer.container.js
@@ -6,7 +6,7 @@ import {
signTokenTx,
signTx,
updateTransaction,
-} from '../../../../store/actions'
+} from '../../../store/actions'
import SendFooter from './send-footer.component'
import {
getGasLimit,
@@ -31,10 +31,21 @@ import {
constructTxParams,
constructUpdatedTx,
} from './send-footer.utils'
+import {
+ getRenderableEstimateDataForSmallButtonsFromGWEI,
+ getDefaultActiveButtonIndex,
+} from '../../../selectors/custom-gas'
export default connect(mapStateToProps, mapDispatchToProps)(SendFooter)
function mapStateToProps (state) {
+ const gasButtonInfo = getRenderableEstimateDataForSmallButtonsFromGWEI(state)
+ const gasPrice = getGasPrice(state)
+ const activeButtonIndex = getDefaultActiveButtonIndex(gasButtonInfo, gasPrice)
+ const gasChangedLabel = activeButtonIndex >= 0
+ ? gasButtonInfo[activeButtonIndex].labelKey
+ : 'custom'
+
return {
amount: getSendAmount(state),
data: getSendHexData(state),
@@ -50,6 +61,7 @@ function mapStateToProps (state) {
tokenBalance: getTokenBalance(state),
unapprovedTxs: getUnapprovedTxs(state),
sendErrors: getSendErrors(state),
+ gasChangedLabel,
}
}
diff --git a/ui/app/components/app/send/send-footer/send-footer.scss b/ui/app/pages/send/send-footer/send-footer.scss
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/app/send/send-footer/send-footer.scss
+++ b/ui/app/pages/send/send-footer/send-footer.scss
diff --git a/ui/app/components/app/send/send-footer/send-footer.selectors.js b/ui/app/pages/send/send-footer/send-footer.selectors.js
index e20addfdc..e20addfdc 100644
--- a/ui/app/components/app/send/send-footer/send-footer.selectors.js
+++ b/ui/app/pages/send/send-footer/send-footer.selectors.js
diff --git a/ui/app/components/app/send/send-footer/send-footer.utils.js b/ui/app/pages/send/send-footer/send-footer.utils.js
index abb2ebc77..91ac29014 100644
--- a/ui/app/components/app/send/send-footer/send-footer.utils.js
+++ b/ui/app/pages/send/send-footer/send-footer.utils.js
@@ -38,6 +38,7 @@ function constructUpdatedTx ({
}) {
const unapprovedTx = unapprovedTxs[editingTransactionId]
const txParamsData = unapprovedTx.txParams.data ? unapprovedTx.txParams.data : data
+
const editingTx = {
...unapprovedTx,
txParams: Object.assign(
diff --git a/ui/app/components/app/send/send-footer/tests/send-footer-component.test.js b/ui/app/pages/send/send-footer/tests/send-footer-component.test.js
index 6683ca8c0..56fc95df2 100644
--- a/ui/app/components/app/send/send-footer/tests/send-footer-component.test.js
+++ b/ui/app/pages/send/send-footer/tests/send-footer-component.test.js
@@ -2,10 +2,10 @@ import React from 'react'
import assert from 'assert'
import { shallow } from 'enzyme'
import sinon from 'sinon'
-import { CONFIRM_TRANSACTION_ROUTE, DEFAULT_ROUTE } from '../../../../../helpers/constants/routes'
+import { CONFIRM_TRANSACTION_ROUTE, DEFAULT_ROUTE } from '../../../../helpers/constants/routes'
import SendFooter from '../send-footer.component.js'
-import PageContainerFooter from '../../../../ui/page-container/page-container-footer'
+import PageContainerFooter from '../../../../components/ui/page-container/page-container-footer'
const propsMethodSpies = {
addToAddressBookIfNew: sinon.spy(),
diff --git a/ui/app/components/app/send/send-footer/tests/send-footer-container.test.js b/ui/app/pages/send/send-footer/tests/send-footer-container.test.js
index 878b0aa19..118ebf356 100644
--- a/ui/app/components/app/send/send-footer/tests/send-footer-container.test.js
+++ b/ui/app/pages/send/send-footer/tests/send-footer-container.test.js
@@ -28,7 +28,7 @@ proxyquire('../send-footer.container.js', {
return () => ({})
},
},
- '../../../../store/actions': actionSpies,
+ '../../../store/actions': actionSpies,
'../send.selectors': {
getGasLimit: (s) => `mockGasLimit:${s}`,
getGasPrice: (s) => `mockGasPrice:${s}`,
@@ -46,6 +46,10 @@ proxyquire('../send-footer.container.js', {
},
'./send-footer.selectors': { isSendFormInError: (s) => `mockInError:${s}` },
'./send-footer.utils': utilsStubs,
+ '../../../selectors/custom-gas': {
+ getRenderableEstimateDataForSmallButtonsFromGWEI: (s) => ([{ labelKey: `mockLabel:${s}` }]),
+ getDefaultActiveButtonIndex: () => 0,
+ },
})
describe('send-footer container', () => {
@@ -68,6 +72,7 @@ describe('send-footer container', () => {
tokenBalance: 'mockTokenBalance:mockState',
unapprovedTxs: 'mockUnapprovedTxs:mockState',
sendErrors: 'mockSendErrors:mockState',
+ gasChangedLabel: 'mockLabel:mockState',
})
})
diff --git a/ui/app/components/app/send/send-footer/tests/send-footer-selectors.test.js b/ui/app/pages/send/send-footer/tests/send-footer-selectors.test.js
index 8de032f57..8de032f57 100644
--- a/ui/app/components/app/send/send-footer/tests/send-footer-selectors.test.js
+++ b/ui/app/pages/send/send-footer/tests/send-footer-selectors.test.js
diff --git a/ui/app/components/app/send/send-footer/tests/send-footer-utils.test.js b/ui/app/pages/send/send-footer/tests/send-footer-utils.test.js
index 28ff0c891..f4705e691 100644
--- a/ui/app/components/app/send/send-footer/tests/send-footer-utils.test.js
+++ b/ui/app/pages/send/send-footer/tests/send-footer-utils.test.js
@@ -149,7 +149,6 @@ describe('send-footer utils', () => {
},
},
})
-
assert.deepEqual(result, {
unapprovedTxParam: 'someOtherParam',
txParams: {
diff --git a/ui/app/components/app/send/send-header/README.md b/ui/app/pages/send/send-header/README.md
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/app/send/send-header/README.md
+++ b/ui/app/pages/send/send-header/README.md
diff --git a/ui/app/components/app/send/send-header/index.js b/ui/app/pages/send/send-header/index.js
index 0b17f0b7d..0b17f0b7d 100644
--- a/ui/app/components/app/send/send-header/index.js
+++ b/ui/app/pages/send/send-header/index.js
diff --git a/ui/app/components/app/send/send-header/send-header.component.js b/ui/app/pages/send/send-header/send-header.component.js
index f216954ef..76e35494a 100644
--- a/ui/app/components/app/send/send-header/send-header.component.js
+++ b/ui/app/pages/send/send-header/send-header.component.js
@@ -1,7 +1,7 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
-import PageContainerHeader from '../../../ui/page-container/page-container-header'
-import { DEFAULT_ROUTE } from '../../../../helpers/constants/routes'
+import PageContainerHeader from '../../../components/ui/page-container/page-container-header'
+import { DEFAULT_ROUTE } from '../../../helpers/constants/routes'
export default class SendHeader extends Component {
@@ -25,7 +25,6 @@ export default class SendHeader extends Component {
return (
<PageContainerHeader
onClose={() => this.onClose()}
- subtitle={this.context.t(...this.props.subtitleParams)}
title={this.context.t(this.props.titleKey)}
/>
)
diff --git a/ui/app/components/app/send/send-header/send-header.container.js b/ui/app/pages/send/send-header/send-header.container.js
index ce53fba9a..1a9c5e9c0 100644
--- a/ui/app/components/app/send/send-header/send-header.container.js
+++ b/ui/app/pages/send/send-header/send-header.container.js
@@ -1,5 +1,5 @@
import { connect } from 'react-redux'
-import { clearSend } from '../../../../store/actions'
+import { clearSend } from '../../../store/actions'
import SendHeader from './send-header.component'
import { getSubtitleParams, getTitleKey } from './send-header.selectors'
diff --git a/ui/app/components/app/send/send-header/send-header.selectors.js b/ui/app/pages/send/send-header/send-header.selectors.js
index d7c9d3766..d7c9d3766 100644
--- a/ui/app/components/app/send/send-header/send-header.selectors.js
+++ b/ui/app/pages/send/send-header/send-header.selectors.js
diff --git a/ui/app/components/app/send/send-header/tests/send-header-component.test.js b/ui/app/pages/send/send-header/tests/send-header-component.test.js
index db2ee8967..91ac7e343 100644
--- a/ui/app/components/app/send/send-header/tests/send-header-component.test.js
+++ b/ui/app/pages/send/send-header/tests/send-header-component.test.js
@@ -2,10 +2,10 @@ import React from 'react'
import assert from 'assert'
import { shallow } from 'enzyme'
import sinon from 'sinon'
-import { DEFAULT_ROUTE } from '../../../../../helpers/constants/routes'
+import { DEFAULT_ROUTE } from '../../../../helpers/constants/routes'
import SendHeader from '../send-header.component.js'
-import PageContainerHeader from '../../../../ui/page-container/page-container-header'
+import PageContainerHeader from '../../../../components/ui/page-container/page-container-header'
const propsMethodSpies = {
clearSend: sinon.spy(),
@@ -57,10 +57,8 @@ describe('SendHeader Component', function () {
it('should pass the correct props to PageContainerHeader', () => {
const {
onClose,
- subtitle,
title,
} = wrapper.find(PageContainerHeader).props()
- assert.equal(subtitle, 'mockSubtitleKeymockVal')
assert.equal(title, 'mockTitleKey')
assert.equal(SendHeader.prototype.onClose.callCount, 0)
onClose()
diff --git a/ui/app/components/app/send/send-header/tests/send-header-container.test.js b/ui/app/pages/send/send-header/tests/send-header-container.test.js
index 634c3424b..fdad8aab3 100644
--- a/ui/app/components/app/send/send-header/tests/send-header-container.test.js
+++ b/ui/app/pages/send/send-header/tests/send-header-container.test.js
@@ -17,7 +17,7 @@ proxyquire('../send-header.container.js', {
return () => ({})
},
},
- '../../../../store/actions': actionSpies,
+ '../../../store/actions': actionSpies,
'./send-header.selectors': {
getTitleKey: (s) => `mockTitleKey:${s}`,
getSubtitleParams: (s) => `mockSubtitleParams:${s}`,
diff --git a/ui/app/components/app/send/send-header/tests/send-header-selectors.test.js b/ui/app/pages/send/send-header/tests/send-header-selectors.test.js
index e0c6a3ab3..e0c6a3ab3 100644
--- a/ui/app/components/app/send/send-header/tests/send-header-selectors.test.js
+++ b/ui/app/pages/send/send-header/tests/send-header-selectors.test.js
diff --git a/ui/app/components/app/send/send.component.js b/ui/app/pages/send/send.component.js
index a38b681b0..5f0c9c9f2 100644
--- a/ui/app/components/app/send/send.component.js
+++ b/ui/app/pages/send/send.component.js
@@ -1,6 +1,6 @@
import React from 'react'
import PropTypes from 'prop-types'
-import PersistentForm from '../../../../lib/persistent-form'
+import PersistentForm from '../../../lib/persistent-form'
import {
getAmountErrorObject,
getGasFeeErrorObject,
@@ -112,6 +112,7 @@ export default class SendTransactionScreen extends PersistentForm {
gasTotal: prevGasTotal,
tokenBalance: prevTokenBalance,
network: prevNetwork,
+ selectedToken: prevSelectedToken,
} = prevProps
const uninitialized = [prevBalance, prevGasTotal].every(n => n === null)
@@ -161,6 +162,13 @@ export default class SendTransactionScreen extends PersistentForm {
this.updateGas()
}
}
+
+ const prevTokenAddress = prevSelectedToken && prevSelectedToken.address
+ const selectedTokenAddress = selectedToken && selectedToken.address
+
+ if (selectedTokenAddress && prevTokenAddress !== selectedTokenAddress) {
+ this.updateSendToken()
+ }
}
componentDidMount () {
@@ -171,18 +179,7 @@ export default class SendTransactionScreen extends PersistentForm {
}
componentWillMount () {
- const {
- from: { address },
- selectedToken,
- tokenContract,
- updateSendTokenBalance,
- } = this.props
-
- updateSendTokenBalance({
- selectedToken,
- tokenContract,
- address,
- })
+ this.updateSendToken()
// Show QR Scanner modal if ?scan=true
if (window.location.search === '?scan=true') {
@@ -199,6 +196,21 @@ export default class SendTransactionScreen extends PersistentForm {
this.props.resetSendState()
}
+ updateSendToken () {
+ const {
+ from: { address },
+ selectedToken,
+ tokenContract,
+ updateSendTokenBalance,
+ } = this.props
+
+ updateSendTokenBalance({
+ selectedToken,
+ tokenContract,
+ address,
+ })
+ }
+
render () {
const { history, showHexData } = this.props
diff --git a/ui/app/components/app/send/send.constants.js b/ui/app/pages/send/send.constants.js
index 36549038e..d3fa38d10 100644
--- a/ui/app/components/app/send/send.constants.js
+++ b/ui/app/pages/send/send.constants.js
@@ -1,5 +1,5 @@
const ethUtil = require('ethereumjs-util')
-const { conversionUtil, multiplyCurrencies } = require('../../../helpers/utils/conversion-util')
+const { conversionUtil, multiplyCurrencies } = require('../../helpers/utils/conversion-util')
const MIN_GAS_PRICE_DEC = '0'
const MIN_GAS_PRICE_HEX = (parseInt(MIN_GAS_PRICE_DEC)).toString(16)
diff --git a/ui/app/components/app/send/send.container.js b/ui/app/pages/send/send.container.js
index e65463b93..69adbb765 100644
--- a/ui/app/components/app/send/send.container.js
+++ b/ui/app/pages/send/send.container.js
@@ -2,6 +2,10 @@ import { connect } from 'react-redux'
import SendEther from './send.component'
import { withRouter } from 'react-router-dom'
import { compose } from 'recompose'
+const {
+ getSelectedAddress,
+} = require('../../selectors/selectors')
+
import {
getAmountConversionRate,
getBlockGasLimit,
@@ -12,7 +16,6 @@ import {
getGasTotal,
getPrimaryCurrency,
getRecentBlocks,
- getSelectedAddress,
getSelectedToken,
getSelectedTokenContract,
getSelectedTokenToFiatRate,
@@ -31,21 +34,21 @@ import {
setGasTotal,
showQrScanner,
qrCodeDetected,
-} from '../../../store/actions'
+} from '../../store/actions'
import {
resetSendState,
updateSendErrors,
-} from '../../../ducks/send/send.duck'
+} from '../../ducks/send/send.duck'
import {
fetchBasicGasEstimates,
-} from '../../../ducks/gas/gas.duck'
+} from '../../ducks/gas/gas.duck'
import {
calcGasTotal,
} from './send.utils.js'
import {
SEND_ROUTE,
-} from '../../../helpers/constants/routes'
+} from '../../helpers/constants/routes'
module.exports = compose(
withRouter,
diff --git a/ui/app/components/app/send/send.scss b/ui/app/pages/send/send.scss
index e69de29bb..e69de29bb 100644
--- a/ui/app/components/app/send/send.scss
+++ b/ui/app/pages/send/send.scss
diff --git a/ui/app/components/app/send/send.selectors.js b/ui/app/pages/send/send.selectors.js
index 2ec677ad1..d4035df28 100644
--- a/ui/app/components/app/send/send.selectors.js
+++ b/ui/app/pages/send/send.selectors.js
@@ -1,18 +1,19 @@
-const { valuesFor } = require('../../../helpers/utils/util')
+const { valuesFor } = require('../../helpers/utils/util')
const abi = require('human-standard-token-abi')
const {
multiplyCurrencies,
-} = require('../../../helpers/utils/conversion-util')
+} = require('../../helpers/utils/conversion-util')
const {
getMetaMaskAccounts,
-} = require('../../../selectors/selectors')
+ getSelectedAddress,
+} = require('../../selectors/selectors')
const {
estimateGasPriceFromRecentBlocks,
calcGasTotal,
} = require('./send.utils')
import {
getFastPriceEstimateInHexWEI,
-} from '../../../selectors/custom-gas'
+} from '../../selectors/custom-gas'
const selectors = {
accountsWithSendEtherInfoSelector,
@@ -33,7 +34,6 @@ const selectors = {
getPrimaryCurrency,
getRecentBlocks,
getSelectedAccount,
- getSelectedAddress,
getSelectedIdentity,
getSelectedToken,
getSelectedTokenContract,
@@ -149,12 +149,6 @@ function getSelectedAccount (state) {
return accounts[selectedAddress]
}
-function getSelectedAddress (state) {
- const selectedAddress = state.metamask.selectedAddress || Object.keys(getMetaMaskAccounts(state))[0]
-
- return selectedAddress
-}
-
function getSelectedIdentity (state) {
const selectedAddress = getSelectedAddress(state)
const identities = state.metamask.identities
@@ -246,9 +240,7 @@ function getSendTo (state) {
function getSendToAccounts (state) {
const fromAccounts = accountsWithSendEtherInfoSelector(state)
const addressBookAccounts = getAddressBook(state)
- const allAccounts = [...fromAccounts, ...addressBookAccounts]
- // TODO: figure out exactly what the below returns and put a descriptive variable name on it
- return Object.entries(allAccounts).map(([key, account]) => account)
+ return [...fromAccounts, ...addressBookAccounts]
}
function getSendWarnings (state) {
diff --git a/ui/app/components/app/send/send.utils.js b/ui/app/pages/send/send.utils.js
index 7609d46ea..4acc174f9 100644
--- a/ui/app/components/app/send/send.utils.js
+++ b/ui/app/pages/send/send.utils.js
@@ -5,10 +5,10 @@ const {
multiplyCurrencies,
conversionGreaterThan,
conversionLessThan,
-} = require('../../../helpers/utils/conversion-util')
+} = require('../../helpers/utils/conversion-util')
const {
calcTokenAmount,
-} = require('../../../helpers/utils/token-util')
+} = require('../../helpers/utils/token-util')
const {
BASE_TOKEN_GAS_COST,
INSUFFICIENT_FUNDS_ERROR,
diff --git a/ui/app/components/app/send/tests/send-component.test.js b/ui/app/pages/send/tests/send-component.test.js
index 738c14839..81955cc1d 100644
--- a/ui/app/components/app/send/tests/send-component.test.js
+++ b/ui/app/pages/send/tests/send-component.test.js
@@ -3,7 +3,7 @@ import assert from 'assert'
import proxyquire from 'proxyquire'
import { shallow } from 'enzyme'
import sinon from 'sinon'
-import timeout from '../../../../../lib/test-timeout'
+import timeout from '../../../../lib/test-timeout'
import SendHeader from '../send-header/send-header.container'
import SendContent from '../send-content/send-content.component'
diff --git a/ui/app/components/app/send/tests/send-container.test.js b/ui/app/pages/send/tests/send-container.test.js
index 9538b67b3..131c42f59 100644
--- a/ui/app/components/app/send/tests/send-container.test.js
+++ b/ui/app/pages/send/tests/send-container.test.js
@@ -24,7 +24,7 @@ proxyquire('../send.container.js', {
},
},
'react-router-dom': { withRouter: () => {} },
- 'recompose': { compose: (arg1, arg2) => () => arg2() },
+ 'recompose': { compose: (_, arg2) => () => arg2() },
'./send.selectors': {
getAmountConversionRate: (s) => `mockAmountConversionRate:${s}`,
getBlockGasLimit: (s) => `mockBlockGasLimit:${s}`,
@@ -35,7 +35,6 @@ proxyquire('../send.container.js', {
getGasTotal: (s) => `mockGasTotal:${s}`,
getPrimaryCurrency: (s) => `mockPrimaryCurrency:${s}`,
getRecentBlocks: (s) => `mockRecentBlocks:${s}`,
- getSelectedAddress: (s) => `mockSelectedAddress:${s}`,
getSelectedToken: (s) => `mockSelectedToken:${s}`,
getSelectedTokenContract: (s) => `mockTokenContract:${s}`,
getSelectedTokenToFiatRate: (s) => `mockTokenToFiatRate:${s}`,
@@ -47,11 +46,15 @@ proxyquire('../send.container.js', {
getTokenBalance: (s) => `mockTokenBalance:${s}`,
getQrCodeData: (s) => `mockQrCodeData:${s}`,
},
- '../../../store/actions': actionSpies,
- '../../../ducks/send/send.duck': duckActionSpies,
+ '../../selectors/selectors': {
+ getSelectedAddress: (s) => `mockSelectedAddress:${s}`,
+ },
+ '../../store/actions': actionSpies,
+ '../../ducks/send/send.duck': duckActionSpies,
'./send.utils.js': {
calcGasTotal: (gasLimit, gasPrice) => gasLimit + gasPrice,
},
+
})
describe('send container', () => {
diff --git a/ui/app/components/app/send/tests/send-selectors-test-data.js b/ui/app/pages/send/tests/send-selectors-test-data.js
index cff26a191..cff26a191 100644
--- a/ui/app/components/app/send/tests/send-selectors-test-data.js
+++ b/ui/app/pages/send/tests/send-selectors-test-data.js
diff --git a/ui/app/components/app/send/tests/send-selectors.test.js b/ui/app/pages/send/tests/send-selectors.test.js
index cdc86fe59..ccc126795 100644
--- a/ui/app/components/app/send/tests/send-selectors.test.js
+++ b/ui/app/pages/send/tests/send-selectors.test.js
@@ -20,7 +20,6 @@ const {
getPrimaryCurrency,
getRecentBlocks,
getSelectedAccount,
- getSelectedAddress,
getSelectedIdentity,
getSelectedToken,
getSelectedTokenContract,
@@ -274,14 +273,6 @@ describe('send selectors', () => {
})
})
- describe('getSelectedAddress()', () => {
- it('should', () => {
- assert.equal(
- getSelectedAddress(mockState),
- '0xd85a4b6a394794842887b8284293d69163007bbb'
- )
- })
- })
describe('getSelectedIdentity()', () => {
it('should return the identity object of the currently selected address', () => {
diff --git a/ui/app/components/app/send/tests/send-utils.test.js b/ui/app/pages/send/tests/send-utils.test.js
index fc4c6deed..bf9cba14a 100644
--- a/ui/app/components/app/send/tests/send-utils.test.js
+++ b/ui/app/pages/send/tests/send-utils.test.js
@@ -9,7 +9,7 @@ import {
const {
addCurrencies,
subtractCurrencies,
-} = require('../../../../helpers/utils/conversion-util')
+} = require('../../../helpers/utils/conversion-util')
const {
INSUFFICIENT_FUNDS_ERROR,
@@ -17,12 +17,12 @@ const {
} = require('../send.constants')
const stubs = {
- addCurrencies: sinon.stub().callsFake((a, b, obj) => {
+ addCurrencies: sinon.stub().callsFake((a, b) => {
if (String(a).match(/^0x.+/)) a = Number(String(a).slice(2))
if (String(b).match(/^0x.+/)) b = Number(String(b).slice(2))
return a + b
}),
- conversionUtil: sinon.stub().callsFake((val, obj) => parseInt(val, 16)),
+ conversionUtil: sinon.stub().callsFake((val) => parseInt(val, 16)),
conversionGTE: sinon.stub().callsFake((obj1, obj2) => obj1.value >= obj2.value),
multiplyCurrencies: sinon.stub().callsFake((a, b) => `${a}x${b}`),
calcTokenAmount: sinon.stub().callsFake((a, d) => 'calc:' + a + d),
@@ -32,7 +32,7 @@ const stubs = {
}
const sendUtils = proxyquire('../send.utils.js', {
- '../../../helpers/utils/conversion-util': {
+ '../../helpers/utils/conversion-util': {
addCurrencies: stubs.addCurrencies,
conversionUtil: stubs.conversionUtil,
conversionGTE: stubs.conversionGTE,
@@ -40,7 +40,7 @@ const sendUtils = proxyquire('../send.utils.js', {
conversionGreaterThan: stubs.conversionGreaterThan,
conversionLessThan: stubs.conversionLessThan,
},
- '../../../helpers/utils/token-util': { calcTokenAmount: stubs.calcTokenAmount },
+ '../../helpers/utils/token-util': { calcTokenAmount: stubs.calcTokenAmount },
'ethereumjs-abi': {
rawEncode: stubs.rawEncode,
},
diff --git a/ui/app/components/app/send/to-autocomplete.component.js b/ui/app/pages/send/to-autocomplete.component.js
index 183967c58..183967c58 100644
--- a/ui/app/components/app/send/to-autocomplete.component.js
+++ b/ui/app/pages/send/to-autocomplete.component.js
diff --git a/ui/app/components/app/send/to-autocomplete/index.js b/ui/app/pages/send/to-autocomplete/index.js
index 244d301d1..244d301d1 100644
--- a/ui/app/components/app/send/to-autocomplete/index.js
+++ b/ui/app/pages/send/to-autocomplete/index.js
diff --git a/ui/app/components/app/send/to-autocomplete/to-autocomplete.js b/ui/app/pages/send/to-autocomplete/to-autocomplete.js
index d3db8cb59..11f86acf3 100644
--- a/ui/app/components/app/send/to-autocomplete/to-autocomplete.js
+++ b/ui/app/pages/send/to-autocomplete/to-autocomplete.js
@@ -1,11 +1,12 @@
const Component = require('react').Component
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
+const copyToClipboard = require('copy-to-clipboard')
const inherits = require('util').inherits
const AccountListItem = require('../account-list-item/account-list-item.component').default
const connect = require('react-redux').connect
-const Tooltip = require('../../../ui/tooltip')
-const checksumAddress = require('../../../../helpers/utils/util').checksumAddress
+const Tooltip = require('../../../components/ui/tooltip')
+const checksumAddress = require('../../../helpers/utils/util').checksumAddress
ToAutoComplete.contextTypes = {
t: PropTypes.func,
@@ -84,7 +85,7 @@ ToAutoComplete.prototype.handleInputEvent = function (event = {}, cb) {
cb && cb(event.target.value)
}
-ToAutoComplete.prototype.componentDidUpdate = function (nextProps, nextState) {
+ToAutoComplete.prototype.componentDidUpdate = function (nextProps) {
if (this.props.to !== nextProps.to) {
this.handleInputEvent()
}
@@ -93,24 +94,34 @@ ToAutoComplete.prototype.componentDidUpdate = function (nextProps, nextState) {
ToAutoComplete.prototype.render = function () {
const {
to,
+ recipient,
dropdownOpen,
onChange,
inError,
qrScanner,
} = this.props
- return h('div.send-v2__to-autocomplete', {}, [
+ const isRecipientToDiff = recipient && recipient !== to
+
+ return h('div.send-v2__to-autocomplete', {style: {
+ borderColor: inError ? 'red' : null,
+ }}, [
h(`input.send-v2__to-autocomplete__input${qrScanner ? '.with-qr' : ''}`, {
placeholder: this.context.t('recipientAddress'),
className: inError ? `send-v2__error-border` : '',
- value: to,
+ value: recipient,
onChange: event => onChange(event.target.value),
onFocus: event => this.handleInputEvent(event),
- style: {
- borderColor: inError ? 'red' : null,
- },
}),
+ isRecipientToDiff && h(Tooltip, {title: this.context.t('copyToClipboard')},
+ h('div.send-v2__to-autocomplete__resolved', {
+ onClick: (event) => {
+ event.preventDefault()
+ event.stopPropagation()
+ copyToClipboard(to)
+ },
+ }, to)),
qrScanner && h(Tooltip, {
title: this.context.t('scanQrCode'),
position: 'bottom',
diff --git a/ui/app/pages/settings/advanced-tab/advanced-tab.component.js b/ui/app/pages/settings/advanced-tab/advanced-tab.component.js
index d1cad1746..3d27fe349 100644
--- a/ui/app/pages/settings/advanced-tab/advanced-tab.component.js
+++ b/ui/app/pages/settings/advanced-tab/advanced-tab.component.js
@@ -24,6 +24,8 @@ export default class AdvancedTab extends PureComponent {
setAdvancedInlineGasFeatureFlag: PropTypes.func,
advancedInlineGas: PropTypes.bool,
showFiatInTestnets: PropTypes.bool,
+ autoLogoutTimeLimit: PropTypes.number,
+ setAutoLogoutTimeLimit: PropTypes.func.isRequired,
setShowFiatConversionOnTestnetsPreference: PropTypes.func.isRequired,
}
@@ -49,7 +51,7 @@ export default class AdvancedTab extends PureComponent {
<TextField
type="text"
id="new-rpc"
- placeholder={t('rpcURL')}
+ placeholder={t('rpcUrl')}
value={newRpc}
onChange={e => this.setState({ newRpc: e.target.value })}
onKeyPress={e => {
@@ -189,7 +191,7 @@ export default class AdvancedTab extends PureComponent {
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<Button
- type="primary"
+ type="secondary"
large
onClick={event => {
event.preventDefault()
@@ -219,7 +221,7 @@ export default class AdvancedTab extends PureComponent {
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<Button
- type="primary"
+ type="secondary"
large
onClick={() => {
window.logStateString((err, result) => {
@@ -251,7 +253,7 @@ export default class AdvancedTab extends PureComponent {
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<Button
- type="secondary"
+ type="warning"
large
className="settings-tab__button--orange"
onClick={event => {
@@ -355,6 +357,48 @@ export default class AdvancedTab extends PureComponent {
)
}
+ renderAutoLogoutTimeLimit () {
+ const { t } = this.context
+ const {
+ autoLogoutTimeLimit,
+ setAutoLogoutTimeLimit,
+ } = this.props
+
+ return (
+ <div className="settings-page__content-row">
+ <div className="settings-page__content-item">
+ <span>{ t('autoLogoutTimeLimit') }</span>
+ <div className="settings-page__content-description">
+ { t('autoLogoutTimeLimitDescription') }
+ </div>
+ </div>
+ <div className="settings-page__content-item">
+ <div className="settings-page__content-item-col">
+ <TextField
+ type="number"
+ id="autoTimeout"
+ placeholder="5"
+ value={this.state.autoLogoutTimeLimit}
+ defaultValue={autoLogoutTimeLimit}
+ onChange={e => this.setState({ autoLogoutTimeLimit: Math.max(Number(e.target.value), 0) })}
+ fullWidth
+ margin="dense"
+ min={0}
+ />
+ <button
+ className="button btn-primary settings-tab__rpc-save-button"
+ onClick={() => {
+ setAutoLogoutTimeLimit(this.state.autoLogoutTimeLimit)
+ }}
+ >
+ { t('save') }
+ </button>
+ </div>
+ </div>
+ </div>
+ )
+ }
+
renderContent () {
const { warning } = this.props
@@ -368,6 +412,7 @@ export default class AdvancedTab extends PureComponent {
{ this.renderAdvancedGasInputInline() }
{ this.renderHexDataOptIn() }
{ this.renderShowConversionInTestnets() }
+ { this.renderAutoLogoutTimeLimit() }
</div>
)
}
diff --git a/ui/app/pages/settings/advanced-tab/advanced-tab.container.js b/ui/app/pages/settings/advanced-tab/advanced-tab.container.js
index 69d7e07e6..bcac55f5e 100644
--- a/ui/app/pages/settings/advanced-tab/advanced-tab.container.js
+++ b/ui/app/pages/settings/advanced-tab/advanced-tab.container.js
@@ -8,10 +8,11 @@ import {
setFeatureFlag,
showModal,
setShowFiatConversionOnTestnetsPreference,
+ setAutoLogoutTimeLimit,
} from '../../../store/actions'
import {preferencesSelector} from '../../../selectors/selectors'
-const mapStateToProps = state => {
+export const mapStateToProps = state => {
const { appState: { warning }, metamask } = state
const {
featureFlags: {
@@ -19,17 +20,18 @@ const mapStateToProps = state => {
advancedInlineGas,
} = {},
} = metamask
- const { showFiatInTestnets } = preferencesSelector(state)
+ const { showFiatInTestnets, autoLogoutTimeLimit } = preferencesSelector(state)
return {
warning,
sendHexData,
advancedInlineGas,
showFiatInTestnets,
+ autoLogoutTimeLimit,
}
}
-const mapDispatchToProps = dispatch => {
+export const mapDispatchToProps = dispatch => {
return {
setHexDataFeatureFlag: shouldShow => dispatch(setFeatureFlag('sendHexData', shouldShow)),
setRpcTarget: (newRpc, chainId, ticker, nickname) => dispatch(updateAndSetCustomRpc(newRpc, chainId, ticker, nickname)),
@@ -39,6 +41,9 @@ const mapDispatchToProps = dispatch => {
setShowFiatConversionOnTestnetsPreference: value => {
return dispatch(setShowFiatConversionOnTestnetsPreference(value))
},
+ setAutoLogoutTimeLimit: value => {
+ return dispatch(setAutoLogoutTimeLimit(value))
+ },
}
}
diff --git a/ui/app/pages/settings/advanced-tab/tests/advanced-tab-component.test.js b/ui/app/pages/settings/advanced-tab/tests/advanced-tab-component.test.js
new file mode 100644
index 000000000..f81329533
--- /dev/null
+++ b/ui/app/pages/settings/advanced-tab/tests/advanced-tab-component.test.js
@@ -0,0 +1,44 @@
+import React from 'react'
+import assert from 'assert'
+import sinon from 'sinon'
+import { shallow } from 'enzyme'
+import AdvancedTab from '../advanced-tab.component'
+import TextField from '../../../../components/ui/text-field'
+
+describe('AdvancedTab Component', () => {
+ it('should render correctly', () => {
+ const root = shallow(
+ <AdvancedTab />,
+ {
+ context: {
+ t: s => `_${s}`,
+ },
+ }
+ )
+
+ assert.equal(root.find('.settings-page__content-row').length, 8)
+ })
+
+ it('should update autoLogoutTimeLimit', () => {
+ const setAutoLogoutTimeLimitSpy = sinon.spy()
+ const root = shallow(
+ <AdvancedTab
+ setAutoLogoutTimeLimit={setAutoLogoutTimeLimitSpy}
+ />,
+ {
+ context: {
+ t: s => `_${s}`,
+ },
+ }
+ )
+
+ const autoTimeout = root.find('.settings-page__content-row').last()
+ const textField = autoTimeout.find(TextField)
+
+ textField.props().onChange({ target: { value: 1440 } })
+ assert.equal(root.state().autoLogoutTimeLimit, 1440)
+
+ autoTimeout.find('button').simulate('click')
+ assert.equal(setAutoLogoutTimeLimitSpy.args[0][0], 1440)
+ })
+})
diff --git a/ui/app/pages/settings/advanced-tab/tests/advanced-tab-container.test.js b/ui/app/pages/settings/advanced-tab/tests/advanced-tab-container.test.js
new file mode 100644
index 000000000..62122073d
--- /dev/null
+++ b/ui/app/pages/settings/advanced-tab/tests/advanced-tab-container.test.js
@@ -0,0 +1,46 @@
+import assert from 'assert'
+import { mapStateToProps, mapDispatchToProps } from '../advanced-tab.container'
+
+const defaultState = {
+ appState: {
+ warning: null,
+ },
+ metamask: {
+ featureFlags: {
+ sendHexData: false,
+ advancedInlineGas: false,
+ },
+ preferences: {
+ autoLogoutTimeLimit: 0,
+ showFiatInTestnets: false,
+ useNativeCurrencyAsPrimaryCurrency: true,
+ },
+ },
+}
+
+describe('AdvancedTab Container', () => {
+ it('should map state to props correctly', () => {
+ const props = mapStateToProps(defaultState)
+ const expected = {
+ warning: null,
+ sendHexData: false,
+ advancedInlineGas: false,
+ showFiatInTestnets: false,
+ autoLogoutTimeLimit: 0,
+ }
+
+ assert.deepEqual(props, expected)
+ })
+
+ it('should map dispatch to props correctly', () => {
+ const props = mapDispatchToProps(() => 'mockDispatch')
+
+ assert.ok(typeof props.setHexDataFeatureFlag === 'function')
+ assert.ok(typeof props.setRpcTarget === 'function')
+ assert.ok(typeof props.displayWarning === 'function')
+ assert.ok(typeof props.showResetAccountConfirmationModal === 'function')
+ assert.ok(typeof props.setAdvancedInlineGasFeatureFlag === 'function')
+ assert.ok(typeof props.setShowFiatConversionOnTestnetsPreference === 'function')
+ assert.ok(typeof props.setAutoLogoutTimeLimit === 'function')
+ })
+})
diff --git a/ui/app/pages/settings/index.scss b/ui/app/pages/settings/index.scss
index 52208dc85..66959ba93 100644
--- a/ui/app/pages/settings/index.scss
+++ b/ui/app/pages/settings/index.scss
@@ -1,5 +1,7 @@
@import 'info-tab/index';
+@import 'networks-tab/index';
+
@import 'settings-tab/index';
.settings-page {
@@ -13,7 +15,6 @@
flex-flow: row nowrap;
padding: 12px 24px;
align-items: center;
- border-bottom: 1px solid $alto;
flex: 0 0 auto;
&__title {
@@ -22,6 +23,45 @@
}
}
+ &__subheader {
+ padding: 16px 4px;
+ font-size: 20px;
+ border-bottom: 1px solid $alto;
+ margin-right: 24px;
+
+ @media screen and (max-width: 575px) {
+ display: none;
+ }
+ }
+
+ &__sub-header {
+ height: 72px;
+ border-bottom: 1px solid #D8D8D8;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+
+ @media screen and (max-width: 575px) {
+ height: 69px;
+ position: relative;
+ text-align: center;
+ }
+ }
+
+ &__sub-header-text {
+ font-family: Roboto;
+ font-style: normal;
+ font-weight: normal;
+ font-size: 24px;
+ line-height: 24px;
+ color: black;
+
+ @media screen and (max-width: 575px) {
+ font-size: 16px;
+ width: 100%;
+ }
+ }
+
&__back-button {
display: none;
@@ -49,8 +89,9 @@
&__content {
display: flex;
flex-flow: row nowrap;
- height: auto;
+ height: 100%;
overflow: auto;
+ border-top: 1px solid #D8D8D8;
&__tabs {
display: flex;
@@ -58,9 +99,15 @@
flex: 1 1 auto;
@media screen and (min-width: 576px) {
- flex: 0 0 32%;
+ flex: 0 0 40%;
max-width: 210px;
- border-right: 1px solid $alto;
+ padding-top: 8px;
+ }
+
+ .tab-bar__tab {
+ @media screen and (min-width: 576px) {
+ padding: 16px 24px 0;
+ }
}
}
@@ -76,6 +123,10 @@
&__body {
padding: 12px 24px;
+
+ @media screen and (min-width: 576px) {
+ padding: 12px;
+ }
}
&__content-row {
@@ -89,7 +140,6 @@
min-width: 0;
display: flex;
flex-direction: column;
- padding: 0 5px;
min-height: 71px;
@media screen and (max-width: 575px) {
diff --git a/ui/app/pages/settings/info-tab/index.scss b/ui/app/pages/settings/info-tab/index.scss
index 43ad6f652..9cc7e21b2 100644
--- a/ui/app/pages/settings/info-tab/index.scss
+++ b/ui/app/pages/settings/info-tab/index.scss
@@ -30,7 +30,7 @@
}
&__link-text {
- color: $curious-blue;
+ @extend %link;
}
&__version-number {
diff --git a/ui/app/pages/settings/networks-tab/index.js b/ui/app/pages/settings/networks-tab/index.js
new file mode 100644
index 000000000..362004498
--- /dev/null
+++ b/ui/app/pages/settings/networks-tab/index.js
@@ -0,0 +1 @@
+export { default } from './networks-tab.container'
diff --git a/ui/app/pages/settings/networks-tab/index.scss b/ui/app/pages/settings/networks-tab/index.scss
new file mode 100644
index 000000000..b0020437d
--- /dev/null
+++ b/ui/app/pages/settings/networks-tab/index.scss
@@ -0,0 +1,200 @@
+.networks-tab {
+ &__content {
+ margin-top: 24px;
+ display: flex;
+ height: 100%;
+ max-width: 739px;
+ justify-content: space-between;
+
+ @media screen and (max-width: 575px) {
+ margin-top: 0px;
+ }
+ }
+
+ &__body {
+ padding: 12px 24px;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+
+ @media screen and (max-width: 575px) {
+ padding: 0;
+ }
+ }
+
+ &__back-button {
+ display: none;
+
+ @media screen and (max-width: 575px) {
+ display: block;
+ background-image: url('/images/caret-left-black.svg');
+ width: 18px;
+ height: 18px;
+ opacity: .5;
+ background-size: contain;
+ background-repeat: no-repeat;
+ background-position: center;
+ margin-right: 16px;
+ cursor: pointer;
+ position: absolute;
+ margin-left: 10px;
+ }
+ }
+
+ &__network-form {
+ flex: 0.5 0 auto;
+ max-width: 343px;
+ max-height: 465px;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+
+ .page-container__footer {
+ border-top: none;
+
+ @media screen and (max-width: 575px) {
+ width: 93%;
+ }
+
+ header {
+ padding: 10px 0px;
+ }
+ }
+
+ @media screen and (max-width: 575px) {
+ display: flex;
+ flex: auto;
+ max-width: 100%;
+ max-height: 100%;
+ align-items: center;
+ width: 100%;
+ margin-top: 10px;
+ }
+ }
+
+ &__network-form-row {
+ @media screen and (max-width: 575px) {
+ display: flex;
+ flex-direction: column;
+ width: 93%;
+ }
+ }
+
+ &__network-form-label {
+ font-family: Roboto;
+ font-style: normal;
+ font-weight: normal;
+ font-size: 14px;
+ line-height: 20px;
+ color: #000000;
+ }
+
+ &__networks-list {
+ flex: 0.5 0 auto;
+ max-width: 343px;
+
+ @media screen and (max-width: 575px) {
+ max-width: 100vw;
+ width: 100vw;
+ overflow-y: scroll;
+ }
+ }
+
+ &__add-network-button-wrapper {
+ display: none;
+
+ @media screen and (max-width: 575px) {
+ display: flex;
+ padding-top: 19px;
+ padding-bottom: 23px;
+ justify-content: center;
+ align-items: center;
+ border-top: 1px solid #D8D8D8;
+
+ .button {
+ width: 178px;
+ }
+ }
+ }
+
+ &__add-network-header-button-wrapper {
+ padding-top: 15px;
+ padding-bottom: 21px;
+ justify-content: center;
+
+ .button {
+ width: 178px;
+ }
+
+ @media screen and (max-width: 575px) {
+ display: none;
+ }
+ }
+
+ &__networks-list--selection {
+ @media screen and (max-width: 575px) {
+ display: none;
+ }
+ }
+
+ &__networks-list-item {
+ display: flex;
+ padding: 13px 0px 13px 17px;
+ position: relative;
+
+ .menu-icon-circle {
+ &:hover {
+ cursor: pointer;
+ }
+ }
+
+ @media screen and (max-width: 575px) {
+ padding: 20px 23px 21px 17px;
+ border-bottom: 1px solid #D8D8D8;
+ }
+ }
+
+ &__networks-list-item:last-of-type {
+ @media screen and (max-width: 575px) {
+ border-bottom: none;
+ }
+ }
+
+ &__networks-list-name {
+ margin-left: 11px;
+ font-family: Roboto;
+ font-style: normal;
+ font-weight: normal;
+ font-size: 16px;
+ line-height: 23px;
+ color: #6A737D;
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+
+ &__networks-list-arrow {
+ display: none;
+
+ @media screen and (max-width: 575px) {
+ display: block;
+ background-image: url('/images/caret-right.svg');
+ width: 24px;
+ height: 24px;
+ background-size: contain;
+ background-repeat: no-repeat;
+ background-position: center;
+ right: 10px;
+ cursor: pointer;
+ position: absolute;
+ width: 24px;
+ height: 24px;
+ }
+ }
+
+ &__networks-list-name--selected {
+ font-weight: bold;
+ color: #000000;
+ }
+} \ No newline at end of file
diff --git a/ui/app/pages/settings/networks-tab/network-form/index.js b/ui/app/pages/settings/networks-tab/network-form/index.js
new file mode 100644
index 000000000..89d9de42b
--- /dev/null
+++ b/ui/app/pages/settings/networks-tab/network-form/index.js
@@ -0,0 +1 @@
+export { default } from './network-form.component'
diff --git a/ui/app/pages/settings/networks-tab/network-form/network-form.component.js b/ui/app/pages/settings/networks-tab/network-form/network-form.component.js
new file mode 100644
index 000000000..5e455b65e
--- /dev/null
+++ b/ui/app/pages/settings/networks-tab/network-form/network-form.component.js
@@ -0,0 +1,225 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import validUrl from 'valid-url'
+import PageContainerFooter from '../../../../components/ui/page-container/page-container-footer'
+import TextField from '../../../../components/ui/text-field'
+
+export default class NetworksTab extends PureComponent {
+ static contextTypes = {
+ t: PropTypes.func.isRequired,
+ metricsEvent: PropTypes.func.isRequired,
+ }
+
+ static propTypes = {
+ editRpc: PropTypes.func.isRequired,
+ rpcUrl: PropTypes.string,
+ chainId: PropTypes.string,
+ ticker: PropTypes.string,
+ viewOnly: PropTypes.bool,
+ networkName: PropTypes.string,
+ onClear: PropTypes.func.isRequired,
+ setRpcTarget: PropTypes.func.isRequired,
+ networksTabIsInAddMode: PropTypes.bool,
+ blockExplorerUrl: PropTypes.string,
+ rpcPrefs: PropTypes.object,
+ }
+
+ state = {
+ rpcUrl: this.props.rpcUrl,
+ chainId: this.props.chainId,
+ ticker: this.props.ticker,
+ networkName: this.props.networkName,
+ blockExplorerUrl: this.props.blockExplorerUrl,
+ errors: {},
+ }
+
+ componentDidUpdate (prevProps) {
+ const { rpcUrl: prevRpcUrl, networksTabIsInAddMode: prevAddMode } = prevProps
+ const {
+ rpcUrl,
+ chainId,
+ ticker,
+ networkName,
+ networksTabIsInAddMode,
+ blockExplorerUrl,
+ } = this.props
+
+ if (!prevAddMode && networksTabIsInAddMode) {
+ this.setState({
+ rpcUrl: '',
+ chainId: '',
+ ticker: '',
+ networkName: '',
+ blockExplorerUrl: '',
+ errors: {},
+ })
+ } else if (prevRpcUrl !== rpcUrl) {
+ this.setState({ rpcUrl, chainId, ticker, networkName, blockExplorerUrl, errors: {} })
+ }
+ }
+
+ componentWillUnmount () {
+ this.props.onClear()
+ this.setState({
+ rpcUrl: '',
+ chainId: '',
+ ticker: '',
+ networkName: '',
+ blockExplorerUrl: '',
+ errors: {},
+ })
+ }
+
+ stateIsUnchanged () {
+ const {
+ rpcUrl,
+ chainId,
+ ticker,
+ networkName,
+ blockExplorerUrl,
+ } = this.props
+
+ const {
+ rpcUrl: stateRpcUrl,
+ chainId: stateChainId,
+ ticker: stateTicker,
+ networkName: stateNetworkName,
+ blockExplorerUrl: stateBlockExplorerUrl,
+ } = this.state
+
+ return (
+ stateRpcUrl === rpcUrl &&
+ stateChainId === chainId &&
+ stateTicker === ticker &&
+ stateNetworkName === networkName &&
+ stateBlockExplorerUrl === blockExplorerUrl
+ )
+ }
+
+ renderFormTextField (fieldKey, textFieldId, onChange, value, optionalTextFieldKey) {
+ const { errors } = this.state
+ const { viewOnly } = this.props
+
+ return (
+ <div className="networks-tab__network-form-row">
+ <div className="networks-tab__network-form-label">{this.context.t(optionalTextFieldKey || fieldKey)}</div>
+ <TextField
+ type="text"
+ id={textFieldId}
+ onChange={onChange}
+ fullWidth
+ margin="dense"
+ value={value}
+ disabled={viewOnly}
+ error={errors[fieldKey]}
+ />
+ </div>
+ )
+ }
+
+ setStateWithValue = (stateKey, validator) => {
+ return (e) => {
+ validator && validator(e.target.value, stateKey)
+ this.setState({ [stateKey]: e.target.value })
+ }
+ }
+
+ setErrorTo = (errorKey, errorVal) => {
+ this.setState({
+ errors: {
+ ...this.state.errors,
+ [errorKey]: errorVal,
+ },
+ })
+ }
+
+ validateChainId = (chainId) => {
+ this.setErrorTo('chainId', !!chainId && Number.isNaN(parseInt(chainId))
+ ? `${this.context.t('invalidInput')} chainId`
+ : ''
+ )
+ }
+
+ validateUrl = (url, stateKey) => {
+ if (validUrl.isWebUri(url)) {
+ this.setErrorTo(stateKey, '')
+ } else {
+ const appendedRpc = `http://${url}`
+ const validWhenAppended = validUrl.isWebUri(appendedRpc) && !url.match(/^https?:\/\/$/)
+
+ this.setErrorTo(stateKey, this.context.t(validWhenAppended ? 'uriErrorMsg' : 'invalidRPC'))
+ }
+ }
+
+ render () {
+ const { setRpcTarget, viewOnly, rpcUrl: propsRpcUrl, editRpc, rpcPrefs = {} } = this.props
+ const {
+ networkName,
+ rpcUrl,
+ chainId,
+ ticker,
+ blockExplorerUrl,
+ errors,
+ } = this.state
+
+
+ return (
+ <div className="networks-tab__network-form">
+ {this.renderFormTextField(
+ 'networkName',
+ 'network-name',
+ this.setStateWithValue('networkName'),
+ networkName,
+ )}
+ {this.renderFormTextField(
+ 'rpcUrl',
+ 'rpc-url',
+ this.setStateWithValue('rpcUrl', this.validateUrl),
+ rpcUrl,
+ )}
+ {this.renderFormTextField(
+ 'chainId',
+ 'chainId',
+ this.setStateWithValue('chainId', this.validateChainId),
+ chainId,
+ 'optionalChainId',
+ )}
+ {this.renderFormTextField(
+ 'symbol',
+ 'network-ticker',
+ this.setStateWithValue('ticker'),
+ ticker,
+ 'optionalSymbol',
+ )}
+ {this.renderFormTextField(
+ 'blockExplorerUrl',
+ 'block-explorer-url',
+ this.setStateWithValue('blockExplorerUrl', this.validateUrl),
+ blockExplorerUrl,
+ 'optionalBlockExplorerUrl',
+ )}
+ <PageContainerFooter
+ cancelText={this.context.t('cancel')}
+ hideCancel={true}
+ onSubmit={() => {
+ if (propsRpcUrl && rpcUrl !== propsRpcUrl) {
+ editRpc(propsRpcUrl, rpcUrl, chainId, ticker, networkName, {
+ blockExplorerUrl: blockExplorerUrl || rpcPrefs.blockExplorerUrl,
+ ...rpcPrefs,
+ })
+ } else {
+ setRpcTarget(rpcUrl, chainId, ticker, networkName, {
+ blockExplorerUrl: blockExplorerUrl || rpcPrefs.blockExplorerUrl,
+ ...rpcPrefs,
+ })
+ }
+ }}
+ submitText={this.context.t('save')}
+ submitButtonType={'confirm'}
+ disabled={viewOnly || this.stateIsUnchanged() || Object.values(errors).some(x => x) || !rpcUrl}
+ />
+ </div>
+ )
+ }
+
+}
diff --git a/ui/app/pages/settings/networks-tab/networks-tab.component.js b/ui/app/pages/settings/networks-tab/networks-tab.component.js
new file mode 100644
index 000000000..2f921a892
--- /dev/null
+++ b/ui/app/pages/settings/networks-tab/networks-tab.component.js
@@ -0,0 +1,214 @@
+import React, { PureComponent } from 'react'
+import PropTypes from 'prop-types'
+import { SETTINGS_ROUTE } from '../../../helpers/constants/routes'
+import { ENVIRONMENT_TYPE_POPUP } from '../../../../../app/scripts/lib/enums'
+import { getEnvironmentType } from '../../../../../app/scripts/lib/util'
+import classnames from 'classnames'
+import Button from '../../../components/ui/button'
+import NetworkForm from './network-form'
+import NetworkDropdownIcon from '../../../components/app/dropdowns/components/network-dropdown-icon'
+
+export default class NetworksTab extends PureComponent {
+ static contextTypes = {
+ t: PropTypes.func.isRequired,
+ metricsEvent: PropTypes.func.isRequired,
+ }
+
+ static propTypes = {
+ editRpc: PropTypes.func.isRequired,
+ history: PropTypes.object.isRequired,
+ location: PropTypes.object.isRequired,
+ networkIsSelected: PropTypes.bool,
+ networksTabIsInAddMode: PropTypes.bool,
+ networksToRender: PropTypes.array.isRequired,
+ selectedNetwork: PropTypes.object,
+ setNetworksTabAddMode: PropTypes.func.isRequired,
+ setRpcTarget: PropTypes.func.isRequired,
+ setSelectedSettingsRpcUrl: PropTypes.func.isRequired,
+ providerUrl: PropTypes.string,
+ providerType: PropTypes.string,
+ networkDefaultedToProvider: PropTypes.bool,
+ }
+
+ componentWillMount () {
+ this.props.setSelectedSettingsRpcUrl(null)
+ }
+
+ isCurrentPath (pathname) {
+ return this.props.location.pathname === pathname
+ }
+
+ renderSubHeader () {
+ const {
+ networkIsSelected,
+ setSelectedSettingsRpcUrl,
+ setNetworksTabAddMode,
+ networksTabIsInAddMode,
+ networkDefaultedToProvider,
+ } = this.props
+
+ return (
+ <div className="settings-page__sub-header">
+ <div
+ className="networks-tab__back-button"
+ onClick={(networkIsSelected && !networkDefaultedToProvider) || networksTabIsInAddMode
+ ? () => {
+ setNetworksTabAddMode(false)
+ setSelectedSettingsRpcUrl(null)
+ }
+ : () => this.props.history.push(SETTINGS_ROUTE)
+ }
+ />
+ <span className="settings-page__sub-header-text">{ this.context.t('networks') }</span>
+ <div className="networks-tab__add-network-header-button-wrapper">
+ <Button
+ type="primary"
+ onClick={event => {
+ event.preventDefault()
+ setSelectedSettingsRpcUrl(null)
+ setNetworksTabAddMode(true)
+ }}
+ >
+ { this.context.t('addNetwork') }
+ </Button>
+ </div>
+ </div>
+ )
+ }
+
+ renderNetworkListItem (network, selectRpcUrl) {
+ const {
+ setSelectedSettingsRpcUrl,
+ setNetworksTabAddMode,
+ networkIsSelected,
+ providerUrl,
+ providerType,
+ networksTabIsInAddMode,
+ } = this.props
+ const {
+ border,
+ iconColor,
+ label,
+ labelKey,
+ rpcUrl,
+ providerType: currentProviderType,
+ } = network
+
+ const listItemNetworkIsSelected = selectRpcUrl && selectRpcUrl === rpcUrl
+ const listItemUrlIsProviderUrl = rpcUrl === providerUrl
+ const listItemTypeIsProviderNonRpcType = providerType !== 'rpc' && currentProviderType === providerType
+ const listItemNetworkIsCurrentProvider = !networkIsSelected && !networksTabIsInAddMode && (listItemUrlIsProviderUrl || listItemTypeIsProviderNonRpcType)
+ const displayNetworkListItemAsSelected = listItemNetworkIsSelected || listItemNetworkIsCurrentProvider
+
+ return (
+ <div
+ key={'settings-network-list-item:' + rpcUrl}
+ className="networks-tab__networks-list-item"
+ onClick={ () => {
+ setNetworksTabAddMode(false)
+ setSelectedSettingsRpcUrl(rpcUrl)
+ }}
+ >
+ <NetworkDropdownIcon
+ backgroundColor={iconColor || 'white'}
+ innerBorder={border}
+ />
+ <div className={ classnames('networks-tab__networks-list-name', {
+ 'networks-tab__networks-list-name--selected': displayNetworkListItemAsSelected,
+ }) }>
+ { label || this.context.t(labelKey) }
+ </div>
+ <div className="networks-tab__networks-list-arrow" />
+ </div>
+ )
+ }
+
+ renderNetworksList () {
+ const { networksToRender, selectedNetwork, networkIsSelected, networksTabIsInAddMode, networkDefaultedToProvider } = this.props
+
+ return (
+ <div className={classnames('networks-tab__networks-list', {
+ 'networks-tab__networks-list--selection': (networkIsSelected && !networkDefaultedToProvider) || networksTabIsInAddMode,
+ })}>
+ { networksToRender.map(network => this.renderNetworkListItem(network, selectedNetwork.rpcUrl)) }
+ </div>
+ )
+ }
+
+ renderNetworksTabContent () {
+ const {
+ setRpcTarget,
+ setSelectedSettingsRpcUrl,
+ setNetworksTabAddMode,
+ selectedNetwork: {
+ labelKey,
+ label,
+ rpcUrl,
+ chainId,
+ ticker,
+ viewOnly,
+ rpcPrefs,
+ blockExplorerUrl,
+ },
+ networksTabIsInAddMode,
+ editRpc,
+ networkDefaultedToProvider,
+ } = this.props
+ const envIsPopup = getEnvironmentType() === ENVIRONMENT_TYPE_POPUP
+
+ return (
+ <div className="networks-tab__content">
+ {this.renderNetworksList()}
+ {networksTabIsInAddMode || !envIsPopup || (envIsPopup && !networkDefaultedToProvider)
+ ? <NetworkForm
+ setRpcTarget={setRpcTarget}
+ editRpc={editRpc}
+ networkName={label || labelKey && this.context.t(labelKey) || ''}
+ rpcUrl={rpcUrl}
+ chainId={chainId}
+ ticker={ticker}
+ onClear={() => {
+ setNetworksTabAddMode(false)
+ setSelectedSettingsRpcUrl(null)
+ }}
+ viewOnly={viewOnly}
+ networksTabIsInAddMode={networksTabIsInAddMode}
+ rpcPrefs={rpcPrefs}
+ blockExplorerUrl={blockExplorerUrl}
+ />
+ : null
+ }
+ </div>
+ )
+ }
+
+ renderContent () {
+ const { setNetworksTabAddMode, setSelectedSettingsRpcUrl, networkIsSelected, networksTabIsInAddMode } = this.props
+
+ return (
+ <div className="networks-tab__body">
+ {this.renderSubHeader()}
+ {this.renderNetworksTabContent()}
+ {!networkIsSelected && !networksTabIsInAddMode
+ ? <div className="networks-tab__add-network-button-wrapper">
+ <Button
+ type="primary"
+ onClick={event => {
+ event.preventDefault()
+ setSelectedSettingsRpcUrl(null)
+ setNetworksTabAddMode(true)
+ }}
+ >
+ { this.context.t('addNetwork') }
+ </Button>
+ </div>
+ : null
+ }
+ </div>
+ )
+ }
+
+ render () {
+ return this.renderContent()
+ }
+}
diff --git a/ui/app/pages/settings/networks-tab/networks-tab.constants.js b/ui/app/pages/settings/networks-tab/networks-tab.constants.js
new file mode 100644
index 000000000..d3d1a01cc
--- /dev/null
+++ b/ui/app/pages/settings/networks-tab/networks-tab.constants.js
@@ -0,0 +1,50 @@
+const defaultNetworksData = [
+ {
+ labelKey: 'mainnet',
+ iconColor: '#29B6AF',
+ providerType: 'mainnet',
+ rpcUrl: 'https://api.infura.io/v1/jsonrpc/mainnet',
+ chainId: '1',
+ ticker: 'ETH',
+ blockExplorerUrl: 'https://etherscan.io',
+ },
+ {
+ labelKey: 'ropsten',
+ iconColor: '#FF4A8D',
+ providerType: 'ropsten',
+ rpcUrl: 'https://api.infura.io/v1/jsonrpc/ropsten',
+ chainId: '3',
+ ticker: 'ETH',
+ blockExplorerUrl: 'https://ropsten.etherscan.io',
+ },
+ {
+ labelKey: 'kovan',
+ iconColor: '#9064FF',
+ providerType: 'kovan',
+ rpcUrl: 'https://api.infura.io/v1/jsonrpc/kovan',
+ chainId: '4',
+ ticker: 'ETH',
+ blockExplorerUrl: 'https://etherscan.io',
+ },
+ {
+ labelKey: 'rinkeby',
+ iconColor: '#F6C343',
+ providerType: 'rinkeby',
+ rpcUrl: 'https://api.infura.io/v1/jsonrpc/rinkeby',
+ chainId: '42',
+ ticker: 'ETH',
+ blockExplorerUrl: 'https://rinkeby.etherscan.io',
+ },
+ {
+ labelKey: 'localhost',
+ iconColor: 'white',
+ border: '1px solid #6A737D',
+ providerType: 'localhost',
+ rpcUrl: 'http://localhost:8545/',
+ blockExplorerUrl: 'https://etherscan.io',
+ },
+]
+
+export {
+ defaultNetworksData,
+}
diff --git a/ui/app/pages/settings/networks-tab/networks-tab.container.js b/ui/app/pages/settings/networks-tab/networks-tab.container.js
new file mode 100644
index 000000000..a5d71f714
--- /dev/null
+++ b/ui/app/pages/settings/networks-tab/networks-tab.container.js
@@ -0,0 +1,77 @@
+import NetworksTab from './networks-tab.component'
+import { compose } from 'recompose'
+import { connect } from 'react-redux'
+import { withRouter } from 'react-router-dom'
+import {
+ setSelectedSettingsRpcUrl,
+ updateAndSetCustomRpc,
+ displayWarning,
+ setNetworksTabAddMode,
+ editRpc,
+} from '../../../store/actions'
+import { defaultNetworksData } from './networks-tab.constants'
+const defaultNetworks = defaultNetworksData.map(network => ({ ...network, viewOnly: true }))
+
+const mapStateToProps = state => {
+ const {
+ frequentRpcListDetail,
+ provider,
+ } = state.metamask
+ const {
+ networksTabSelectedRpcUrl,
+ networksTabIsInAddMode,
+ } = state.appState
+
+ const frequentRpcNetworkListDetails = frequentRpcListDetail.map(rpc => {
+ return {
+ label: rpc.nickname,
+ iconColor: '#6A737D',
+ providerType: 'rpc',
+ rpcUrl: rpc.rpcUrl,
+ chainId: rpc.chainId,
+ ticker: rpc.ticker,
+ blockExplorerUrl: rpc.rpcPrefs && rpc.rpcPrefs.blockExplorerUrl || '',
+ }
+ })
+
+ const networksToRender = [ ...defaultNetworks, ...frequentRpcNetworkListDetails ]
+ let selectedNetwork = networksToRender.find(network => network.rpcUrl === networksTabSelectedRpcUrl) || {}
+ const networkIsSelected = Boolean(selectedNetwork.rpcUrl)
+
+ let networkDefaultedToProvider = false
+ if (!networkIsSelected && !networksTabIsInAddMode) {
+ selectedNetwork = networksToRender.find(network => {
+ return network.rpcUrl === provider.rpcTarget || network.providerType !== 'rpc' && network.providerType === provider.type
+ }) || {}
+ networkDefaultedToProvider = true
+ }
+
+ return {
+ selectedNetwork,
+ networksToRender,
+ networkIsSelected,
+ networksTabIsInAddMode,
+ providerType: provider.type,
+ providerUrl: provider.rpcTarget,
+ networkDefaultedToProvider,
+ }
+}
+
+const mapDispatchToProps = dispatch => {
+ return {
+ setSelectedSettingsRpcUrl: newRpcUrl => dispatch(setSelectedSettingsRpcUrl(newRpcUrl)),
+ setRpcTarget: (newRpc, chainId, ticker, nickname, rpcPrefs) => {
+ dispatch(updateAndSetCustomRpc(newRpc, chainId, ticker, nickname, rpcPrefs))
+ },
+ displayWarning: warning => dispatch(displayWarning(warning)),
+ setNetworksTabAddMode: isInAddMode => dispatch(setNetworksTabAddMode(isInAddMode)),
+ editRpc: (oldRpc, newRpc, chainId, ticker, nickname, rpcPrefs) => {
+ dispatch(editRpc(oldRpc, newRpc, chainId, ticker, nickname, rpcPrefs))
+ },
+ }
+}
+
+export default compose(
+ withRouter,
+ connect(mapStateToProps, mapDispatchToProps)
+)(NetworksTab)
diff --git a/ui/app/pages/settings/security-tab/security-tab.component.js b/ui/app/pages/settings/security-tab/security-tab.component.js
index 233561115..01a28bac7 100644
--- a/ui/app/pages/settings/security-tab/security-tab.component.js
+++ b/ui/app/pages/settings/security-tab/security-tab.component.js
@@ -39,7 +39,7 @@ export default class SecurityTab extends PureComponent {
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<Button
- type="primary"
+ type="secondary"
large
onClick={() => {
window.logStateString((err, result) => {
@@ -73,7 +73,7 @@ export default class SecurityTab extends PureComponent {
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<Button
- type="secondary"
+ type="warning"
large
className="settings-tab__button--orange"
onClick={event => {
@@ -101,7 +101,7 @@ export default class SecurityTab extends PureComponent {
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<Button
- type="secondary"
+ type="danger"
large
onClick={event => {
event.preventDefault()
diff --git a/ui/app/pages/settings/settings-tab/index.scss b/ui/app/pages/settings/settings-tab/index.scss
index ef32b0e4c..c1750af2c 100644
--- a/ui/app/pages/settings/settings-tab/index.scss
+++ b/ui/app/pages/settings/settings-tab/index.scss
@@ -6,19 +6,15 @@
}
&__advanced-link {
- color: $curious-blue;
+ @extend %small-link;
padding-left: 5px;
}
&__rpc-save-button {
align-self: flex-end;
padding: 5px;
- text-transform: uppercase;
- color: $dusty-gray;
cursor: pointer;
width: 25%;
- min-width: 80px;
- height: 33px;
}
&__button--red {
@@ -35,20 +31,6 @@
}
}
- &__button--orange {
- border-color: lighten($ecstasy, 20%);
- color: $ecstasy;
-
- &:active {
- background: lighten($ecstasy, 40%);
- border-color: $ecstasy;
- }
-
- &:hover {
- border-color: $ecstasy;
- }
- }
-
&__radio-buttons {
display: flex;
align-items: center;
diff --git a/ui/app/pages/settings/settings.component.js b/ui/app/pages/settings/settings.component.js
index 061e65060..a2f137264 100644
--- a/ui/app/pages/settings/settings.component.js
+++ b/ui/app/pages/settings/settings.component.js
@@ -1,11 +1,12 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
-import { Switch, Route, matchPath } from 'react-router-dom'
+import { Switch, Route, matchPath, withRouter } from 'react-router-dom'
import { ENVIRONMENT_TYPE_POPUP } from '../../../../app/scripts/lib/enums'
import { getEnvironmentType } from '../../../../app/scripts/lib/util'
import TabBar from '../../components/app/tab-bar'
import c from 'classnames'
import SettingsTab from './settings-tab'
+import NetworksTab from './networks-tab'
import AdvancedTab from './advanced-tab'
import InfoTab from './info-tab'
import SecurityTab from './security-tab'
@@ -16,6 +17,7 @@ import {
GENERAL_ROUTE,
ABOUT_US_ROUTE,
SETTINGS_ROUTE,
+ NETWORKS_ROUTE,
} from '../../helpers/constants/routes'
const ROUTES_TO_I18N_KEYS = {
@@ -25,7 +27,7 @@ const ROUTES_TO_I18N_KEYS = {
[ABOUT_US_ROUTE]: 'about',
}
-export default class SettingsPage extends PureComponent {
+class SettingsPage extends PureComponent {
static propTypes = {
location: PropTypes.object,
history: PropTypes.object,
@@ -55,7 +57,7 @@ export default class SettingsPage extends PureComponent {
>
<div className="settings-page__header">
{
- !this.isCurrentPath(SETTINGS_ROUTE) && (
+ !this.isCurrentPath(SETTINGS_ROUTE) && !this.isCurrentPath(NETWORKS_ROUTE) && (
<div
className="settings-page__back-button"
onClick={() => history.push(SETTINGS_ROUTE)}
@@ -75,6 +77,7 @@ export default class SettingsPage extends PureComponent {
{ this.renderTabs() }
</div>
<div className="settings-page__content__modules">
+ { this.renderSubHeader() }
{ this.renderContent() }
</div>
</div>
@@ -82,6 +85,17 @@ export default class SettingsPage extends PureComponent {
)
}
+ renderSubHeader () {
+ const { t } = this.context
+ const { location: { pathname } } = this.props
+
+ return (
+ <div className="settings-page__subheader">
+ {t(ROUTES_TO_I18N_KEYS[pathname] || 'general')}
+ </div>
+ )
+ }
+
renderTabs () {
const { history, location } = this.props
const { t } = this.context
@@ -92,6 +106,7 @@ export default class SettingsPage extends PureComponent {
{ content: t('general'), description: t('generalSettingsDescription'), key: GENERAL_ROUTE },
{ content: t('advanced'), description: t('advancedSettingsDescription'), key: ADVANCED_ROUTE },
{ content: t('securityAndPrivacy'), description: t('securitySettingsDescription'), key: SECURITY_ROUTE },
+ { content: t('networks'), description: t('networkSettingsDescription'), key: NETWORKS_ROUTE },
{ content: t('about'), description: t('aboutSettingsDescription'), key: ABOUT_US_ROUTE },
]}
isActive={key => {
@@ -125,6 +140,11 @@ export default class SettingsPage extends PureComponent {
/>
<Route
exact
+ path={NETWORKS_ROUTE}
+ component={NetworksTab}
+ />
+ <Route
+ exact
path={SECURITY_ROUTE}
component={SecurityTab}
/>
@@ -135,3 +155,5 @@ export default class SettingsPage extends PureComponent {
)
}
}
+
+export default withRouter(SettingsPage)
diff --git a/ui/app/selectors/custom-gas.js b/ui/app/selectors/custom-gas.js
index ecffb37ca..5ba786f0f 100644
--- a/ui/app/selectors/custom-gas.js
+++ b/ui/app/selectors/custom-gas.js
@@ -18,7 +18,7 @@ import {
} from '../helpers/utils/formatters'
import {
calcGasTotal,
-} from '../components/app/send/send.utils'
+} from '../pages/send/send.utils'
import { addHexPrefix } from 'ethereumjs-util'
const selectors = {
diff --git a/ui/app/selectors/selectors.js b/ui/app/selectors/selectors.js
index 554232f7b..c7cb80024 100644
--- a/ui/app/selectors/selectors.js
+++ b/ui/app/selectors/selectors.js
@@ -48,6 +48,8 @@ const selectors = {
getNumberOfAccounts,
getNumberOfTokens,
isEthereumNetwork,
+ getMetaMetricState,
+ getRpcPrefsForCurrentProvider,
}
module.exports = selectors
@@ -91,7 +93,8 @@ function getAccountType (state) {
}
function getSelectedAsset (state) {
- return getSelectedToken(state) || 'ETH'
+ const selectedToken = getSelectedToken(state)
+ return selectedToken && selectedToken.symbol || 'ETH'
}
function getCurrentNetworkId (state) {
@@ -164,7 +167,7 @@ function getSelectedToken (state) {
const tokens = state.metamask.tokens || []
const selectedTokenAddress = state.metamask.selectedTokenAddress
const selectedToken = tokens.filter(({ address }) => address === selectedTokenAddress)[0]
- const sendToken = state.metamask.send.token
+ const sendToken = state.metamask.send && state.metamask.send.token
return selectedToken || sendToken || null
}
@@ -300,9 +303,10 @@ function isEthereumNetwork (state) {
MAINNET,
RINKEBY,
ROPSTEN,
+ GOERLI,
} = NETWORK_TYPES
- return [ KOVAN, MAINNET, RINKEBY, ROPSTEN].includes(networkType)
+ return [ KOVAN, MAINNET, RINKEBY, ROPSTEN, GOERLI].includes(networkType)
}
function preferencesSelector ({ metamask }) {
@@ -312,3 +316,22 @@ function preferencesSelector ({ metamask }) {
function getAdvancedInlineGasShown (state) {
return Boolean(state.metamask.featureFlags.advancedInlineGas)
}
+
+function getMetaMetricState (state) {
+ return {
+ network: getCurrentNetworkId(state),
+ activeCurrency: getSelectedAsset(state),
+ accountType: getAccountType(state),
+ metaMetricsId: state.metamask.metaMetricsId,
+ numberOfTokens: getNumberOfTokens(state),
+ numberOfAccounts: getNumberOfAccounts(state),
+ participateInMetaMetrics: state.metamask.participateInMetaMetrics,
+ }
+}
+
+function getRpcPrefsForCurrentProvider (state) {
+ const { frequentRpcListDetail, provider } = state.metamask
+ const selectRpcInfo = frequentRpcListDetail.find(rpcInfo => rpcInfo.rpcUrl === provider.rpcTarget)
+ const { rpcPrefs = {} } = selectRpcInfo || {}
+ return rpcPrefs
+}
diff --git a/ui/app/store/actions.js b/ui/app/store/actions.js
index 7d369fdb9..7f6cbea1f 100644
--- a/ui/app/store/actions.js
+++ b/ui/app/store/actions.js
@@ -5,7 +5,7 @@ const { getTokenAddressFromTokenObject } = require('../helpers/utils/util')
const {
calcTokenBalance,
estimateGas,
-} = require('../components/app/send/send.utils')
+} = require('../pages/send/send.utils')
const ethUtil = require('ethereumjs-util')
const { fetchLocale } = require('../helpers/utils/i18n-helper')
const log = require('loglevel')
@@ -239,6 +239,7 @@ var actions = {
updateAndSetCustomRpc: updateAndSetCustomRpc,
setRpcTarget: setRpcTarget,
delRpcTarget: delRpcTarget,
+ editRpc: editRpc,
setProviderType: setProviderType,
SET_HARDWARE_WALLET_DEFAULT_HD_PATH: 'SET_HARDWARE_WALLET_DEFAULT_HD_PATH',
setHardwareWalletDefaultHdPath,
@@ -316,6 +317,7 @@ var actions = {
UPDATE_PREFERENCES: 'UPDATE_PREFERENCES',
setUseNativeCurrencyAsPrimaryCurrencyPreference,
setShowFiatConversionOnTestnetsPreference,
+ setAutoLogoutTimeLimit,
// Migration of users to new UI
setCompletedUiMigration,
@@ -343,12 +345,21 @@ var actions = {
createCancelTransaction,
createSpeedUpTransaction,
- approveProviderRequest,
- rejectProviderRequest,
+ approveProviderRequestByOrigin,
+ rejectProviderRequestByOrigin,
clearApprovedOrigins,
setFirstTimeFlowType,
SET_FIRST_TIME_FLOW_TYPE: 'SET_FIRST_TIME_FLOW_TYPE',
+
+ SET_SELECTED_SETTINGS_RPC_URL: 'SET_SELECTED_SETTINGS_RPC_URL',
+ setSelectedSettingsRpcUrl,
+ SET_NETWORKS_TAB_ADD_MODE: 'SET_NETWORKS_TAB_ADD_MODE',
+ setNetworksTabAddMode,
+
+ // AppStateController-related actions
+ SET_LAST_ACTIVE_TIME: 'SET_LAST_ACTIVE_TIME',
+ setLastActiveTime,
}
module.exports = actions
@@ -761,7 +772,7 @@ function addNewAccount () {
function checkHardwareStatus (deviceName, hdPath) {
log.debug(`background.checkHardwareStatus`, deviceName, hdPath)
- return (dispatch, getState) => {
+ return (dispatch) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
background.checkHardwareStatus(deviceName, hdPath, (err, unlocked) => {
@@ -782,10 +793,10 @@ function checkHardwareStatus (deviceName, hdPath) {
function forgetDevice (deviceName) {
log.debug(`background.forgetDevice`, deviceName)
- return (dispatch, getState) => {
+ return (dispatch) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
- background.forgetDevice(deviceName, (err, response) => {
+ background.forgetDevice(deviceName, (err) => {
if (err) {
log.error(err)
dispatch(actions.displayWarning(err.message))
@@ -803,7 +814,7 @@ function forgetDevice (deviceName) {
function connectHardware (deviceName, page, hdPath) {
log.debug(`background.connectHardware`, deviceName, page, hdPath)
- return (dispatch, getState) => {
+ return (dispatch) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
background.connectHardware(deviceName, page, hdPath, (err, accounts) => {
@@ -824,10 +835,10 @@ function connectHardware (deviceName, page, hdPath) {
function unlockHardwareWalletAccount (index, deviceName, hdPath) {
log.debug(`background.unlockHardwareWalletAccount`, index, deviceName, hdPath)
- return (dispatch, getState) => {
+ return (dispatch) => {
dispatch(actions.showLoadingIndication())
return new Promise((resolve, reject) => {
- background.unlockHardwareWalletAccount(index, deviceName, hdPath, (err, accounts) => {
+ background.unlockHardwareWalletAccount(index, deviceName, hdPath, (err) => {
if (err) {
log.error(err)
dispatch(actions.displayWarning(err.message))
@@ -848,7 +859,7 @@ function showInfoPage () {
}
function showQrScanner (ROUTE) {
- return (dispatch, getState) => {
+ return (dispatch) => {
return WebcamUtils.checkStatus()
.then(status => {
if (!status.environmentReady) {
@@ -987,7 +998,7 @@ function signTypedMsg (msgData) {
function signTx (txData) {
return (dispatch) => {
- global.ethQuery.sendTransaction(txData, (err, data) => {
+ global.ethQuery.sendTransaction(txData, (err) => {
if (err) {
return dispatch(actions.displayWarning(err.message))
}
@@ -1020,7 +1031,6 @@ function setGasTotal (gasTotal) {
function updateGasData ({
gasPrice,
blockGasLimit,
- recentBlocks,
selectedAddress,
selectedToken,
to,
@@ -1402,7 +1412,7 @@ function cancelTx (txData) {
* @return {function(*): Promise<void>}
*/
function cancelTxs (txDataList) {
- return async (dispatch, getState) => {
+ return async (dispatch) => {
window.onbeforeunload = null
dispatch(actions.showLoadingIndication())
const txIds = txDataList.map(({id}) => id)
@@ -1807,7 +1817,7 @@ function removeSuggestedTokens () {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
window.onbeforeunload = null
- return new Promise((resolve, reject) => {
+ return new Promise((resolve) => {
background.removeSuggestedTokens((err, suggestedTokens) => {
dispatch(actions.hideLoadingIndication())
if (err) {
@@ -1826,7 +1836,7 @@ function removeSuggestedTokens () {
}
function addKnownMethodData (fourBytePrefix, methodData) {
- return (dispatch) => {
+ return () => {
background.addKnownMethodData(fourBytePrefix, methodData)
}
}
@@ -1931,7 +1941,7 @@ function setProviderType (type) {
return (dispatch, getState) => {
const { type: currentProviderType } = getState().metamask.provider
log.debug(`background.setProviderType`, type)
- background.setProviderType(type, (err, result) => {
+ background.setProviderType(type, (err) => {
if (err) {
log.error(err)
return dispatch(actions.displayWarning('Had a problem changing networks!'))
@@ -1958,10 +1968,10 @@ function setPreviousProvider (type) {
}
}
-function updateAndSetCustomRpc (newRpc, chainId, ticker = 'ETH', nickname) {
+function updateAndSetCustomRpc (newRpc, chainId, ticker = 'ETH', nickname, rpcPrefs) {
return (dispatch) => {
log.debug(`background.updateAndSetCustomRpc: ${newRpc} ${chainId} ${ticker} ${nickname}`)
- background.updateAndSetCustomRpc(newRpc, chainId, ticker, nickname || newRpc, (err, result) => {
+ background.updateAndSetCustomRpc(newRpc, chainId, ticker, nickname || newRpc, rpcPrefs, (err) => {
if (err) {
log.error(err)
return dispatch(actions.displayWarning('Had a problem changing networks!'))
@@ -1974,10 +1984,33 @@ function updateAndSetCustomRpc (newRpc, chainId, ticker = 'ETH', nickname) {
}
}
+function editRpc (oldRpc, newRpc, chainId, ticker = 'ETH', nickname, rpcPrefs) {
+ return (dispatch) => {
+ log.debug(`background.delRpcTarget: ${oldRpc}`)
+ background.delCustomRpc(oldRpc, (err) => {
+ if (err) {
+ log.error(err)
+ return dispatch(self.displayWarning('Had a problem removing network!'))
+ }
+ dispatch(actions.setSelectedToken())
+ background.updateAndSetCustomRpc(newRpc, chainId, ticker, nickname || newRpc, rpcPrefs, (err) => {
+ if (err) {
+ log.error(err)
+ return dispatch(actions.displayWarning('Had a problem changing networks!'))
+ }
+ dispatch({
+ type: actions.SET_RPC_TARGET,
+ value: newRpc,
+ })
+ })
+ })
+ }
+}
+
function setRpcTarget (newRpc, chainId, ticker = 'ETH', nickname) {
return (dispatch) => {
log.debug(`background.setRpcTarget: ${newRpc} ${chainId} ${ticker} ${nickname}`)
- background.setCustomRpc(newRpc, chainId, ticker, nickname || newRpc, (err, result) => {
+ background.setCustomRpc(newRpc, chainId, ticker, nickname || newRpc, (err) => {
if (err) {
log.error(err)
return dispatch(actions.displayWarning('Had a problem changing networks!'))
@@ -1990,7 +2023,7 @@ function setRpcTarget (newRpc, chainId, ticker = 'ETH', nickname) {
function delRpcTarget (oldRpc) {
return (dispatch) => {
log.debug(`background.delRpcTarget: ${oldRpc}`)
- background.delCustomRpc(oldRpc, (err, result) => {
+ background.delCustomRpc(oldRpc, (err) => {
if (err) {
log.error(err)
return dispatch(self.displayWarning('Had a problem removing network!'))
@@ -2000,11 +2033,12 @@ function delRpcTarget (oldRpc) {
}
}
+
// Calls the addressBookController to add a new address.
function addToAddressBook (recipient, nickname = '') {
log.debug(`background.addToAddressBook`)
return (dispatch) => {
- background.setAddressBook(recipient, nickname, (err, result) => {
+ background.setAddressBook(recipient, nickname, (err) => {
if (err) {
log.error(err)
return dispatch(self.displayWarning('Address book failed to update'))
@@ -2273,7 +2307,7 @@ function pairUpdate (coin) {
}
}
-function shapeShiftSubview (network) {
+function shapeShiftSubview () {
var pair = 'btc_eth'
return (dispatch) => {
dispatch(actions.showSubLoadingIndication())
@@ -2309,7 +2343,7 @@ function coinShiftRquest (data, marketData) {
}
function buyWithShapeShift (data) {
- return dispatch => new Promise((resolve, reject) => {
+ return () => new Promise((resolve, reject) => {
shapeShiftRequest('shift', { method: 'POST', data}, (response) => {
if (response.error) {
return reject(response.error)
@@ -2356,7 +2390,7 @@ function shapeShiftRequest (query, options, cb) {
!options ? options = {} : null
options.method ? method = options.method : method = 'GET'
- var requestListner = function (request) {
+ var requestListner = function () {
try {
queryResponse = JSON.parse(this.responseText)
cb ? cb(queryResponse) : null
@@ -2439,6 +2473,10 @@ function setShowFiatConversionOnTestnetsPreference (value) {
return setPreference('showFiatInTestnets', value)
}
+function setAutoLogoutTimeLimit (value) {
+ return setPreference('autoLogoutTimeLimit', value)
+}
+
function setCompletedOnboarding () {
return async dispatch => {
dispatch(actions.showLoadingIndication())
@@ -2680,20 +2718,20 @@ function setPendingTokens (pendingTokens) {
}
}
-function approveProviderRequest (tabID) {
- return (dispatch) => {
- background.approveProviderRequest(tabID)
+function approveProviderRequestByOrigin (origin) {
+ return () => {
+ background.approveProviderRequestByOrigin(origin)
}
}
-function rejectProviderRequest (tabID) {
- return (dispatch) => {
- background.rejectProviderRequest(tabID)
+function rejectProviderRequestByOrigin (origin) {
+ return () => {
+ background.rejectProviderRequestByOrigin(origin)
}
}
function clearApprovedOrigins () {
- return (dispatch) => {
+ return () => {
background.clearApprovedOrigins()
}
}
@@ -2712,3 +2750,27 @@ function setFirstTimeFlowType (type) {
})
}
}
+
+function setSelectedSettingsRpcUrl (newRpcUrl) {
+ return {
+ type: actions.SET_SELECTED_SETTINGS_RPC_URL,
+ value: newRpcUrl,
+ }
+}
+
+function setNetworksTabAddMode (isInAddMode) {
+ return {
+ type: actions.SET_NETWORKS_TAB_ADD_MODE,
+ value: isInAddMode,
+ }
+}
+
+function setLastActiveTime () {
+ return (dispatch) => {
+ background.setLastActiveTime((err) => {
+ if (err) {
+ return dispatch(actions.displayWarning(err.message))
+ }
+ })
+ }
+}
diff --git a/ui/example.js b/ui/example.js
index 4627c0e9c..d940d3bc8 100644
--- a/ui/example.js
+++ b/ui/example.js
@@ -91,7 +91,7 @@ accountManager.setSelectedAccount = function (address, cb) {
this._didUpdate()
}
-accountManager.signTransaction = function (txParams, cb) {
+accountManager.signTransaction = function () {
alert('signing tx....')
}
diff --git a/ui/lib/account-link.js b/ui/lib/account-link.js
index 037d990fa..f2e321991 100644
--- a/ui/lib/account-link.js
+++ b/ui/lib/account-link.js
@@ -1,4 +1,8 @@
-module.exports = function (address, network) {
+module.exports = function (address, network, rpcPrefs) {
+ if (rpcPrefs && rpcPrefs.blockExplorerUrl) {
+ return `${rpcPrefs.blockExplorerUrl}/address/${address}`
+ }
+
const net = parseInt(network)
let link
switch (net) {
@@ -17,6 +21,9 @@ module.exports = function (address, network) {
case 42: // kovan test net
link = `https://kovan.etherscan.io/address/${address}`
break
+ case 5: // goerli test net
+ link = `https://goerli.etherscan.io/address/${address}`
+ break
default:
link = ''
break
diff --git a/ui/lib/etherscan-prefix-for-network.js b/ui/lib/etherscan-prefix-for-network.js
index 2c1904f1c..ce194b0a8 100644
--- a/ui/lib/etherscan-prefix-for-network.js
+++ b/ui/lib/etherscan-prefix-for-network.js
@@ -14,6 +14,9 @@ module.exports = function (network) {
case 42: // kovan test net
prefix = 'kovan.'
break
+ case 5: // goerli test net
+ prefix = 'goerli.'
+ break
default:
prefix = ''
}
diff --git a/ui/lib/test-timeout.js b/ui/lib/test-timeout.js
index 957b0fce2..7d825487f 100644
--- a/ui/lib/test-timeout.js
+++ b/ui/lib/test-timeout.js
@@ -1,5 +1,5 @@
export default function timeout (time) {
- return new Promise((resolve, reject) => {
+ return new Promise((resolve) => {
setTimeout(resolve, time || 1500)
})
}