From 466ece458882f28556af8abf424dc7642a9e15e8 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Thu, 5 Dec 2019 16:23:43 -0400 Subject: [PATCH 01/70] Revert "Merge pull request #7599 from MetaMask/Version-v7.7.0" (#7648) This reverts commit 1110287fe153bba44b8a7504611c2648b50e065f, reversing changes made to 72eb233ee953c381407f0121f76822a79753e4ee. --- .circleci/config.yml | 12 - .eslintrc | 26 +- .gitattributes | 1 - CHANGELOG.md | 13 - README.md | 2 +- app/_locales/am/messages.json | 44 +- app/_locales/ar/messages.json | 44 +- app/_locales/bg/messages.json | 48 +- app/_locales/bn/messages.json | 44 +- app/_locales/ca/messages.json | 50 +- app/_locales/cs/messages.json | 33 ++ app/_locales/da/messages.json | 48 +- app/_locales/de/messages.json | 48 +- app/_locales/el/messages.json | 48 +- app/_locales/en/messages.json | 135 ++--- app/_locales/es/messages.json | 44 +- app/_locales/es_419/messages.json | 48 +- app/_locales/et/messages.json | 52 +- app/_locales/fa/messages.json | 44 +- app/_locales/fi/messages.json | 48 +- app/_locales/fil/messages.json | 42 +- app/_locales/fr/messages.json | 48 +- app/_locales/gu/messages.json | 9 + app/_locales/he/messages.json | 44 +- app/_locales/hi/messages.json | 44 +- app/_locales/hn/messages.json | 33 ++ app/_locales/hr/messages.json | 48 +- app/_locales/ht/messages.json | 33 ++ app/_locales/hu/messages.json | 48 +- app/_locales/id/messages.json | 48 +- app/_locales/it/messages.json | 66 ++- app/_locales/ja/messages.json | 37 +- app/_locales/kn/messages.json | 48 +- app/_locales/ko/messages.json | 48 +- app/_locales/lt/messages.json | 48 +- app/_locales/lv/messages.json | 46 +- app/_locales/ml/messages.json | 9 + app/_locales/mr/messages.json | 9 + app/_locales/ms/messages.json | 48 +- app/_locales/nl/messages.json | 33 ++ app/_locales/no/messages.json | 48 +- app/_locales/ph/messages.json | 30 ++ app/_locales/pl/messages.json | 48 +- app/_locales/pt/messages.json | 33 ++ app/_locales/pt_BR/messages.json | 48 +- app/_locales/pt_PT/messages.json | 9 + app/_locales/ro/messages.json | 48 +- app/_locales/ru/messages.json | 44 +- app/_locales/sk/messages.json | 48 +- app/_locales/sl/messages.json | 48 +- app/_locales/sr/messages.json | 48 +- app/_locales/sv/messages.json | 48 +- app/_locales/sw/messages.json | 48 +- app/_locales/ta/messages.json | 36 ++ app/_locales/te/messages.json | 9 + app/_locales/th/messages.json | 38 +- app/_locales/tr/messages.json | 29 + app/_locales/uk/messages.json | 48 +- app/_locales/vi/messages.json | 30 ++ app/_locales/zh_CN/messages.json | 46 +- app/_locales/zh_TW/messages.json | 44 +- app/images/broken-line.svg | 3 - app/images/connect-white.svg | 3 - ...-check.svg => provider-approval-check.svg} | 0 app/manifest.json | 2 +- app/scripts/background.js | 30 +- app/scripts/contentscript.js | 141 +++-- app/scripts/controllers/app-state.js | 2 +- app/scripts/controllers/detect-tokens.js | 42 +- .../controllers/incoming-transactions.js | 4 +- .../controllers/network/middleware/pending.js | 16 +- app/scripts/controllers/network/network.js | 8 +- app/scripts/controllers/onboarding.js | 46 +- app/scripts/controllers/permissions/index.js | 377 ------------- .../permissions/loggerMiddleware.js | 169 ------ .../permissions/methodMiddleware.js | 90 ---- .../permissions/permissions-safe-methods.json | 49 -- .../permissions/restrictedMethods.js | 20 - app/scripts/controllers/preferences.js | 62 +-- app/scripts/controllers/provider-approval.js | 175 ++++++ app/scripts/controllers/recent-blocks.js | 8 +- app/scripts/controllers/threebox.js | 4 +- app/scripts/controllers/token-rates.js | 20 +- app/scripts/controllers/transactions/index.js | 110 ++-- .../lib/tx-state-history-helper.js | 8 +- .../controllers/transactions/lib/util.js | 12 +- .../transactions/pending-tx-tracker.js | 20 +- .../controllers/transactions/tx-gas-utils.js | 8 +- .../transactions/tx-state-manager.js | 31 +- app/scripts/createStandardProvider.js | 73 +++ app/scripts/edge-encryptor.js | 28 +- app/scripts/inpage.js | 118 +++- app/scripts/lib/account-tracker.js | 12 +- app/scripts/lib/auto-reload.js | 21 +- app/scripts/lib/buy-eth-url.js | 12 +- app/scripts/lib/cleanErrorStack.js | 4 +- app/scripts/lib/createDnodeRemoteGetter.js | 4 +- app/scripts/lib/createLoggerMiddleware.js | 4 +- app/scripts/lib/createOriginMiddleware.js | 1 - app/scripts/lib/ens-ipfs/resolver.js | 2 - app/scripts/lib/ens-ipfs/setup.js | 12 +- app/scripts/lib/local-store.js | 19 +- app/scripts/lib/message-manager.js | 22 +- app/scripts/lib/migrator/index.js | 8 +- app/scripts/lib/nodeify.js | 6 +- app/scripts/lib/notification-manager.js | 20 +- app/scripts/lib/pending-balance-calculator.js | 4 +- app/scripts/lib/personal-message-manager.js | 22 +- app/scripts/lib/select-chain-id.js | 4 +- app/scripts/lib/setupFetchDebugging.js | 4 +- app/scripts/lib/setupMetamaskMeshMetrics.js | 4 +- app/scripts/lib/setupSentry.js | 12 +- app/scripts/lib/stream-utils.js | 4 +- app/scripts/lib/typed-message-manager.js | 28 +- app/scripts/lib/util.js | 29 - app/scripts/metamask-controller.js | 350 +++++------- app/scripts/migrations/004.js | 5 +- app/scripts/migrations/015.js | 7 +- app/scripts/migrations/016.js | 4 +- app/scripts/migrations/017.js | 4 +- app/scripts/migrations/019.js | 4 +- app/scripts/migrations/022.js | 4 +- app/scripts/migrations/023.js | 11 +- app/scripts/migrations/024.js | 4 +- app/scripts/migrations/025.js | 8 +- app/scripts/migrations/029.js | 1 + app/scripts/migrations/031.js | 2 +- app/scripts/migrations/040.js | 23 - app/scripts/platforms/extension.js | 54 +- development/announcer.js | 12 +- development/auto-changelog.sh | 2 +- development/mock-3box.js | 4 +- development/mock-dev.js | 62 +-- development/rollback.sh | 14 +- development/selector.js | 81 ++- development/show-deps-install-scripts.js | 20 +- development/sourcemap-validator.js | 12 +- development/static-server.js | 92 ---- development/verify-locale-strings.js | 4 +- development/version-bump.js | 2 - docs/porting_to_new_environment.md | 13 +- gulpfile.js | 4 +- package.json | 23 +- test/e2e/contract-test/contract.js | 431 ++++++--------- test/e2e/contract-test/index.html | 104 ++-- test/e2e/ethereum-on.spec.js | 28 +- test/e2e/helpers.js | 2 - test/e2e/metamask-responsive-ui.spec.js | 2 +- test/e2e/metamask-ui.spec.js | 24 +- test/e2e/permissions.spec.js | 201 ------- test/e2e/run-all.sh | 8 - test/e2e/run-web3.sh | 2 +- test/e2e/signature-request.spec.js | 29 +- test/e2e/web3.spec.js | 37 +- test/helper.js | 16 +- test/integration/index.js | 4 +- test/lib/mock-encryptor.js | 4 +- test/lib/mock-simple-keychain.js | 8 +- test/lib/react-trigger-change.js | 24 +- test/lib/util.js | 8 +- test/setup.js | 8 +- test/unit/actions/config_test.js | 16 +- .../unit/actions/set_selected_account_test.js | 18 +- test/unit/actions/tx_test.js | 16 +- test/unit/actions/view_info_test.js | 14 +- test/unit/actions/warning_test.js | 12 +- test/unit/app/buy-eth-url.spec.js | 4 +- .../app/controllers/detect-tokens-test.js | 10 +- .../controllers/metamask-controller-test.js | 24 +- .../network/pending-middleware-test.js | 8 +- .../preferences-controller-test.js | 47 +- .../app/controllers/provider-approval-test.js | 330 ++++++++++++ .../transactions/pending-tx-test.js | 36 +- .../transactions/tx-controller-test.js | 46 +- .../tx-state-history-helper-test.js | 4 +- .../controllers/transactions/tx-utils-test.js | 24 +- test/unit/app/edge-encryptor-test.js | 4 +- test/unit/app/message-manager-test.js | 16 +- test/unit/app/nodeify-test.js | 16 +- .../unit/app/personal-message-manager-test.js | 28 +- test/unit/app/typed-message-manager.spec.js | 12 +- test/unit/migrations/023-test.js | 4 +- test/unit/migrations/024-test.js | 7 +- test/unit/migrations/025-test.js | 8 +- test/unit/migrations/027-test.js | 4 +- test/unit/migrations/029-test.js | 4 +- test/unit/migrations/migrator-test.js | 4 +- test/unit/reducers/unlock_vault_test.js | 10 +- .../responsive/components/dropdown-test.js | 32 +- test/unit/test-utils.js | 4 +- test/unit/ui/app/actions.spec.js | 7 +- test/unit/util_test.js | 124 +++-- test/web3/schema.js | 4 +- test/web3/web3.js | 2 +- .../account-details.component.js | 18 +- .../account-details.container.js | 17 +- .../components/app/account-details/index.scss | 6 - .../account-menu/account-menu.component.js | 35 +- .../account-menu/account-menu.container.js | 9 +- ui/app/components/app/account-menu/index.scss | 4 - ui/app/components/app/account-panel.js | 64 ++- .../app/app-header/app-header.component.js | 3 +- ui/app/components/app/bn-as-decimal-input.js | 188 +++++++ ...onfirm-page-container-summary.component.js | 8 +- ...confirm-page-container-header.component.js | 54 +- ...irm-page-container-navigation.component.js | 24 +- .../confirm-page-container.component.js | 20 +- .../connected-sites-list.component.js | 157 ------ .../connected-sites-list.container.js | 57 -- .../app/connected-sites-list/index.js | 1 - .../app/connected-sites-list/index.scss | 125 ----- .../recipient-group.component.js | 8 +- ui/app/components/app/copyable.js | 53 ++ .../app/customize-gas-modal/gas-modal-card.js | 48 +- .../app/customize-gas-modal/gas-slider.js | 50 ++ .../app/customize-gas-modal/index.js | 132 +++-- .../app/dropdowns/account-details-dropdown.js | 178 +++--- .../app/dropdowns/components/dropdown.js | 70 +-- .../app/dropdowns/components/menu.js | 94 ++-- .../components/network-dropdown-icon.js | 85 ++- .../app/dropdowns/network-dropdown.js | 505 +++++++++--------- .../app/dropdowns/simple-dropdown.js | 98 ++-- .../app/dropdowns/tests/menu.test.js | 14 +- .../tests/network-dropdown-icon.test.js | 14 +- .../dropdowns/tests/network-dropdown.test.js | 2 +- .../app/dropdowns/token-menu-dropdown.js | 51 +- .../advanced-gas-inputs.component.js | 22 +- .../advanced-tab-content.component.js | 22 +- .../advanced-tab-content-component.test.js | 26 +- .../basic-tab-content.component.js | 12 +- .../tests/basic-tab-content-component.test.js | 8 +- .../gas-modal-page-container.component.js | 15 +- ...gas-modal-page-container-component.test.js | 84 ++- .../gas-price-button-group.component.js | 34 +- .../gas-price-button-group-component.test.js | 8 +- .../gas-price-chart/gas-price-chart.utils.js | 4 +- .../gas-slider/gas-slider.component.js | 2 +- ui/app/components/app/index.scss | 12 +- ui/app/components/app/input-number.js | 62 +-- .../loading-network-screen.component.js | 62 +-- ui/app/components/app/menu-droppo.js | 82 +-- .../components/app/modal/modal.component.js | 41 +- .../account-details-modal.component.js | 18 +- .../app/modals/account-modal-container.js | 80 +++ .../account-modal-container.component.js | 52 -- .../account-modal-container.container.js | 20 - .../modals/account-modal-container/index.js | 1 - .../clear-approved-origins.component.js | 39 ++ .../clear-approved-origins.container.js} | 12 +- .../modals/clear-approved-origins/index.js | 1 + .../confirm-remove-account.component.js | 4 +- .../app/modals/deposit-ether-modal.js | 213 ++++++++ .../deposit-ether-modal.component.js | 168 ------ .../deposit-ether-modal.container.js | 34 -- .../app/modals/deposit-ether-modal/index.js | 1 - .../disconnect-account.component.js | 52 -- .../disconnect-account.container.js | 44 -- .../app/modals/disconnect-account/index.js | 1 - .../app/modals/disconnect-account/index.scss | 25 - .../disconnect-all.component.js | 54 -- .../app/modals/disconnect-all/index.js | 1 - .../app/modals/disconnect-all/index.scss | 38 -- .../edit-approval-permission.component.js | 40 +- .../app/modals/export-private-key-modal.js | 178 ++++++ .../export-private-key-modal.component.js | 168 ------ .../export-private-key-modal.container.js | 38 -- .../modals/export-private-key-modal/index.js | 1 - .../modals/hide-token-confirmation-modal.js | 76 +-- ui/app/components/app/modals/index.scss | 6 - .../metametrics-opt-in-modal.component.js | 3 +- ui/app/components/app/modals/modal.js | 191 +++---- .../app/modals/new-account-modal/index.js | 1 - .../app/modals/new-account-modal/index.scss | 37 -- .../new-account-modal.component.js | 78 --- .../new-account-modal.container.js | 44 -- .../app/modals/notification-modal.js | 90 ++-- .../multiple-notifications.component.js | 10 +- .../network-display.component.js | 32 +- ui/app/components/app/network.js | 197 +++---- .../app/permission-page-container/index.js | 3 - .../app/permission-page-container/index.scss | 281 ---------- .../index.js | 1 - ...ission-page-container-content.component.js | 163 ------ .../permission-page-container-header/index.js | 1 - .../permission-page-container.component.js | 151 ------ .../permission-page-container.container.js | 28 - .../app/provider-page-container/index.js | 3 + .../app/provider-page-container/index.scss | 121 +++++ .../provider-page-container-content/index.js | 1 + ...ovider-page-container-content.component.js | 87 +++ ...ovider-page-container-content.container.js | 11 + .../provider-page-container-header/index.js | 1 + ...ovider-page-container-header.component.js} | 4 +- .../provider-page-container.component.js | 107 ++++ .../tests/selected-account-component.test.js | 10 +- ui/app/components/app/shift-list-item.js | 204 +++++++ .../components/app/shift-list-item/index.js | 1 - .../shift-list-item.component.js | 240 --------- .../shift-list-item.container.js | 12 - .../app/sidebars/sidebar.component.js | 16 +- .../sidebars/tests/sidebars-component.test.js | 14 +- .../app/signature-request-original.js | 357 +++++++++++++ .../app/signature-request-original/index.js | 1 - .../signature-request-original.component.js | 324 ----------- .../signature-request-original.container.js | 72 --- .../signature-request-header.component.js | 10 +- .../tests/signature-request.test.js | 15 +- ui/app/components/app/token-cell.js | 177 ++++++ ui/app/components/app/token-cell/index.js | 1 - .../app/token-cell/token-cell.component.js | 141 ----- .../app/token-cell/token-cell.container.js | 25 - ui/app/components/app/token-list.js | 117 ++-- .../transaction-action.component.test.js | 12 +- .../transaction-breakdown.component.js | 26 +- ...transaction-list-item-details.component.js | 21 +- .../app/transaction-list-item/index.scss | 1 - .../transaction-list-item.component.js | 18 +- .../token-view-balance.component.test.js | 38 +- ui/app/components/app/wallet-view.js | 178 ++++++ ui/app/components/app/wallet-view/index.js | 1 - .../app/wallet-view/wallet-view.component.js | 147 ----- .../app/wallet-view/wallet-view.container.js | 33 -- ui/app/components/ui/alert/index.js | 29 +- .../ui/button-group/button-group.stories.js | 8 +- .../tests/button-group-component.test.js | 16 +- ui/app/components/ui/button/button.stories.js | 28 +- ui/app/components/ui/copyButton.js | 120 ++--- .../tests/currency-display.component.test.js | 22 +- ui/app/components/ui/editable-label.js | 96 ++-- ui/app/components/ui/eth-balance.js | 102 ++++ .../ui/eth-balance/eth-balance.component.js | 137 ----- .../ui/eth-balance/eth-balance.container.js | 10 - ui/app/components/ui/eth-balance/index.js | 1 - .../export-text-container.component.js | 49 +- ui/app/components/ui/fiat-value.js | 68 ++- .../tests/hex-to-decimal.component.test.js | 20 +- .../icon-with-fallback.component.js | 42 -- .../components/ui/icon-with-fallback/index.js | 1 - .../ui/icon-with-fallback/index.scss | 30 -- .../tests/identicon.component.test.js | 2 +- .../loading-screen.component.js | 34 +- ui/app/components/ui/mascot.js | 23 +- .../page-container-footer.component.js | 22 +- .../page-container-footer.component.test.js | 18 +- .../page-container-header.component.js | 26 +- .../page-container-header.component.test.js | 20 +- ui/app/components/ui/qr-code.js | 95 ++-- ui/app/components/ui/readonly-input.js | 27 +- .../sender-to-recipient.component.js | 7 +- ui/app/components/ui/snackbar/index.js | 1 - ui/app/components/ui/snackbar/index.scss | 11 - .../ui/snackbar/snackbar.component.js | 18 - .../ui/text-field/text-field.stories.js | 28 +- ui/app/components/ui/tooltip.js | 42 +- .../ui/unit-input/unit-input.component.js | 4 +- ui/app/css/itcss/components/confirm.scss | 4 + ui/app/css/itcss/components/index.scss | 1 - ui/app/css/itcss/components/network.scss | 1 + .../css/itcss/components/newui-sections.scss | 4 + ui/app/css/itcss/components/pages/index.scss | 2 +- ...n-approval.scss => provider-approval.scss} | 4 +- ui/app/css/itcss/components/sections.scss | 1 + ui/app/css/itcss/components/send.scss | 63 +++ .../itcss/components/transaction-list.scss | 8 + ui/app/css/itcss/settings/variables.scss | 29 - ui/app/ducks/app/app.js | 22 +- ui/app/ducks/index.js | 4 +- ui/app/ducks/metamask/metamask.js | 4 +- ui/app/helpers/constants/routes.js | 4 - ui/app/helpers/utils/util.js | 125 ++--- ui/app/pages/add-token/add-token.component.js | 6 +- .../token-list/token-list.component.js | 3 +- .../confirm-approve-content.component.js | 57 +- .../confirm-approve.component.js | 40 +- .../confirm-approve.container.js | 4 +- .../confirm-transaction-base.component.js | 68 ++- .../confirm-transaction-base.container.js | 8 +- ui/app/pages/confirm-transaction/conf-tx.js | 59 +- .../confirm-transaction.component.js | 4 +- .../connected-sites.component.js | 36 -- ui/app/pages/connected-sites/index.js | 1 - ui/app/pages/connected-sites/index.scss | 37 -- .../connect-hardware/account-list.js | 223 ++++---- .../connect-hardware/connect-screen.js | 406 +++++++------- .../create-account/connect-hardware/index.js | 77 ++- .../create-account.component.js | 18 +- .../create-account/import-account/index.js | 69 +-- .../create-account/import-account/json.js | 109 ++-- .../import-account/private-key.js | 77 ++- .../create-account/import-account/seed.js | 35 ++ .../create-account/new-account.component.js | 11 +- .../import-with-seed-phrase.component.js | 21 +- .../new-account/new-account.component.js | 28 +- .../end-of-flow/end-of-flow.component.js | 48 +- .../end-of-flow/end-of-flow.container.js | 7 +- .../first-time-flow/end-of-flow/index.scss | 2 +- .../first-time-flow.selectors.js | 28 +- .../metametrics-opt-in.component.js | 3 +- .../onboarding-initiator-util.js | 48 -- .../draggable-seed.component.js | 3 +- .../seed-phrase/reveal-seed-phrase/index.scss | 2 +- .../reveal-seed-phrase.component.js | 47 +- .../reveal-seed-phrase.container.js | 9 +- ui/app/pages/home/home.component.js | 99 ++-- ui/app/pages/home/home.container.js | 12 +- ui/app/pages/index.scss | 4 - ui/app/pages/keychains/reveal-seed.js | 171 +++--- ui/app/pages/mobile-sync/index.js | 416 ++++++++++++++- .../mobile-sync/mobile-sync.component.js | 436 --------------- .../mobile-sync/mobile-sync.container.js | 25 - .../choose-account.component.js | 108 ---- .../choose-account/index.js | 1 - .../choose-account/index.scss | 97 ---- ui/app/pages/permissions-connect/index.js | 1 - ui/app/pages/permissions-connect/index.scss | 11 - .../permissions-connect-footer/index.js | 1 - .../permissions-connect-footer/index.scss | 27 - .../permissions-connect-footer.component.js | 27 - .../permissions-connect-header/index.js | 1 - .../permissions-connect-header/index.scss | 15 - .../permissions-connect-header.component.js | 25 - .../permissions-connect.component.js | 208 -------- .../permissions-connect.container.js | 66 --- ui/app/pages/provider-approval/index.js | 1 + .../provider-approval.component.js | 36 ++ .../provider-approval.container.js | 12 + ui/app/pages/routes/index.js | 68 +-- .../account-list-item.component.js | 72 ++- .../tests/account-list-item-component.test.js | 24 +- .../add-recipient/add-recipient.component.js | 3 +- .../add-recipient/ens-input.component.js | 17 +- .../tests/add-recipient-component.test.js | 36 +- .../tests/amount-max-button-component.test.js | 20 +- .../tests/send-amount-row-component.test.js | 36 +- .../send-asset-row.component.js | 3 +- .../send-dropdown-list.component.js | 40 +- .../send-dropdown-list-component.test.js | 24 +- .../gas-fee-display.component.js | 16 +- .../test/gas-fee-display.component.test.js | 18 +- .../send-gas-row/send-gas-row.component.js | 117 ++-- .../tests/send-gas-row-component.test.js | 30 +- .../send-row-error-message-component.test.js | 10 +- .../tests/send-row-wrapper-component.test.js | 52 +- .../tests/send-footer-component.test.js | 90 ++-- .../tests/send-header-component.test.js | 12 +- ui/app/pages/send/send.component.js | 5 +- ui/app/pages/send/send.utils.js | 12 +- .../pages/send/tests/send-component.test.js | 92 ++-- ui/app/pages/send/tests/send-utils.test.js | 8 +- .../pages/send/to-autocomplete.component.js | 141 +++++ ui/app/pages/send/to-autocomplete/index.js | 1 + .../send/to-autocomplete/to-autocomplete.js | 121 +++++ .../connected-site-row.component.js | 31 ++ .../connected-site-row/index.js | 1 + .../connected-site-row/index.scss | 14 + .../connections-tab.component.js | 133 +++++ .../connections-tab.container.js | 39 ++ .../pages/settings/connections-tab/index.js | 1 + .../pages/settings/connections-tab/index.scss | 1 + .../add-contact/add-contact.component.js | 18 +- .../contact-list-tab.component.js | 49 +- .../edit-contact/edit-contact.component.js | 2 +- ui/app/pages/settings/index.scss | 13 + .../networks-tab/networks-tab.component.js | 34 +- ui/app/pages/settings/settings.component.js | 28 +- ui/app/selectors/selectors.js | 189 ------- ui/app/store/actions.js | 153 ++---- ui/index.js | 22 +- ui/lib/icon-factory.js | 16 +- ui/lib/persistent-form.js | 4 +- yarn.lock | 197 +++---- 471 files changed, 10284 insertions(+), 11518 deletions(-) delete mode 100644 app/images/broken-line.svg delete mode 100644 app/images/connect-white.svg rename app/images/{permissions-check.svg => provider-approval-check.svg} (100%) delete mode 100644 app/scripts/controllers/permissions/index.js delete mode 100644 app/scripts/controllers/permissions/loggerMiddleware.js delete mode 100644 app/scripts/controllers/permissions/methodMiddleware.js delete mode 100644 app/scripts/controllers/permissions/permissions-safe-methods.json delete mode 100644 app/scripts/controllers/permissions/restrictedMethods.js create mode 100644 app/scripts/controllers/provider-approval.js create mode 100644 app/scripts/createStandardProvider.js delete mode 100644 app/scripts/migrations/040.js delete mode 100644 development/static-server.js delete mode 100644 test/e2e/permissions.spec.js create mode 100644 test/unit/app/controllers/provider-approval-test.js create mode 100644 ui/app/components/app/bn-as-decimal-input.js delete mode 100644 ui/app/components/app/connected-sites-list/connected-sites-list.component.js delete mode 100644 ui/app/components/app/connected-sites-list/connected-sites-list.container.js delete mode 100644 ui/app/components/app/connected-sites-list/index.js delete mode 100644 ui/app/components/app/connected-sites-list/index.scss create mode 100644 ui/app/components/app/copyable.js create mode 100644 ui/app/components/app/customize-gas-modal/gas-slider.js create mode 100644 ui/app/components/app/modals/account-modal-container.js delete mode 100644 ui/app/components/app/modals/account-modal-container/account-modal-container.component.js delete mode 100644 ui/app/components/app/modals/account-modal-container/account-modal-container.container.js delete mode 100644 ui/app/components/app/modals/account-modal-container/index.js create mode 100644 ui/app/components/app/modals/clear-approved-origins/clear-approved-origins.component.js rename ui/app/components/app/modals/{disconnect-all/disconnect-all.container.js => clear-approved-origins/clear-approved-origins.container.js} (53%) create mode 100644 ui/app/components/app/modals/clear-approved-origins/index.js create mode 100644 ui/app/components/app/modals/deposit-ether-modal.js delete mode 100644 ui/app/components/app/modals/deposit-ether-modal/deposit-ether-modal.component.js delete mode 100644 ui/app/components/app/modals/deposit-ether-modal/deposit-ether-modal.container.js delete mode 100644 ui/app/components/app/modals/deposit-ether-modal/index.js delete mode 100644 ui/app/components/app/modals/disconnect-account/disconnect-account.component.js delete mode 100644 ui/app/components/app/modals/disconnect-account/disconnect-account.container.js delete mode 100644 ui/app/components/app/modals/disconnect-account/index.js delete mode 100644 ui/app/components/app/modals/disconnect-account/index.scss delete mode 100644 ui/app/components/app/modals/disconnect-all/disconnect-all.component.js delete mode 100644 ui/app/components/app/modals/disconnect-all/index.js delete mode 100644 ui/app/components/app/modals/disconnect-all/index.scss create mode 100644 ui/app/components/app/modals/export-private-key-modal.js delete mode 100644 ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.component.js delete mode 100644 ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.container.js delete mode 100644 ui/app/components/app/modals/export-private-key-modal/index.js delete mode 100644 ui/app/components/app/modals/new-account-modal/index.js delete mode 100644 ui/app/components/app/modals/new-account-modal/index.scss delete mode 100644 ui/app/components/app/modals/new-account-modal/new-account-modal.component.js delete mode 100644 ui/app/components/app/modals/new-account-modal/new-account-modal.container.js delete mode 100644 ui/app/components/app/permission-page-container/index.js delete mode 100644 ui/app/components/app/permission-page-container/index.scss delete mode 100644 ui/app/components/app/permission-page-container/permission-page-container-content/index.js delete mode 100644 ui/app/components/app/permission-page-container/permission-page-container-content/permission-page-container-content.component.js delete mode 100644 ui/app/components/app/permission-page-container/permission-page-container-header/index.js delete mode 100644 ui/app/components/app/permission-page-container/permission-page-container.component.js delete mode 100644 ui/app/components/app/permission-page-container/permission-page-container.container.js create mode 100644 ui/app/components/app/provider-page-container/index.js create mode 100644 ui/app/components/app/provider-page-container/index.scss create mode 100644 ui/app/components/app/provider-page-container/provider-page-container-content/index.js create mode 100644 ui/app/components/app/provider-page-container/provider-page-container-content/provider-page-container-content.component.js create mode 100644 ui/app/components/app/provider-page-container/provider-page-container-content/provider-page-container-content.container.js create mode 100644 ui/app/components/app/provider-page-container/provider-page-container-header/index.js rename ui/app/components/app/{permission-page-container/permission-page-container-header/permission-page-container-header.component.js => provider-page-container/provider-page-container-header/provider-page-container-header.component.js} (58%) create mode 100644 ui/app/components/app/provider-page-container/provider-page-container.component.js create mode 100644 ui/app/components/app/shift-list-item.js delete mode 100644 ui/app/components/app/shift-list-item/index.js delete mode 100644 ui/app/components/app/shift-list-item/shift-list-item.component.js delete mode 100644 ui/app/components/app/shift-list-item/shift-list-item.container.js create mode 100644 ui/app/components/app/signature-request-original.js delete mode 100644 ui/app/components/app/signature-request-original/index.js delete mode 100644 ui/app/components/app/signature-request-original/signature-request-original.component.js delete mode 100644 ui/app/components/app/signature-request-original/signature-request-original.container.js create mode 100644 ui/app/components/app/token-cell.js delete mode 100644 ui/app/components/app/token-cell/index.js delete mode 100644 ui/app/components/app/token-cell/token-cell.component.js delete mode 100644 ui/app/components/app/token-cell/token-cell.container.js create mode 100644 ui/app/components/app/wallet-view.js delete mode 100644 ui/app/components/app/wallet-view/index.js delete mode 100644 ui/app/components/app/wallet-view/wallet-view.component.js delete mode 100644 ui/app/components/app/wallet-view/wallet-view.container.js create mode 100644 ui/app/components/ui/eth-balance.js delete mode 100644 ui/app/components/ui/eth-balance/eth-balance.component.js delete mode 100644 ui/app/components/ui/eth-balance/eth-balance.container.js delete mode 100644 ui/app/components/ui/eth-balance/index.js delete mode 100644 ui/app/components/ui/icon-with-fallback/icon-with-fallback.component.js delete mode 100644 ui/app/components/ui/icon-with-fallback/index.js delete mode 100644 ui/app/components/ui/icon-with-fallback/index.scss delete mode 100644 ui/app/components/ui/snackbar/index.js delete mode 100644 ui/app/components/ui/snackbar/index.scss delete mode 100644 ui/app/components/ui/snackbar/snackbar.component.js rename ui/app/css/itcss/components/pages/{permission-approval.scss => provider-approval.scss} (66%) delete mode 100644 ui/app/pages/connected-sites/connected-sites.component.js delete mode 100644 ui/app/pages/connected-sites/index.js delete mode 100644 ui/app/pages/connected-sites/index.scss create mode 100644 ui/app/pages/create-account/import-account/seed.js delete mode 100644 ui/app/pages/first-time-flow/onboarding-initiator-util.js delete mode 100644 ui/app/pages/mobile-sync/mobile-sync.component.js delete mode 100644 ui/app/pages/mobile-sync/mobile-sync.container.js delete mode 100644 ui/app/pages/permissions-connect/choose-account/choose-account.component.js delete mode 100644 ui/app/pages/permissions-connect/choose-account/index.js delete mode 100644 ui/app/pages/permissions-connect/choose-account/index.scss delete mode 100644 ui/app/pages/permissions-connect/index.js delete mode 100644 ui/app/pages/permissions-connect/index.scss delete mode 100644 ui/app/pages/permissions-connect/permissions-connect-footer/index.js delete mode 100644 ui/app/pages/permissions-connect/permissions-connect-footer/index.scss delete mode 100644 ui/app/pages/permissions-connect/permissions-connect-footer/permissions-connect-footer.component.js delete mode 100644 ui/app/pages/permissions-connect/permissions-connect-header/index.js delete mode 100644 ui/app/pages/permissions-connect/permissions-connect-header/index.scss delete mode 100644 ui/app/pages/permissions-connect/permissions-connect-header/permissions-connect-header.component.js delete mode 100644 ui/app/pages/permissions-connect/permissions-connect.component.js delete mode 100644 ui/app/pages/permissions-connect/permissions-connect.container.js create mode 100644 ui/app/pages/provider-approval/index.js create mode 100644 ui/app/pages/provider-approval/provider-approval.component.js create mode 100644 ui/app/pages/provider-approval/provider-approval.container.js create mode 100644 ui/app/pages/send/to-autocomplete.component.js create mode 100644 ui/app/pages/send/to-autocomplete/index.js create mode 100644 ui/app/pages/send/to-autocomplete/to-autocomplete.js create mode 100644 ui/app/pages/settings/connections-tab/connected-site-row/connected-site-row.component.js create mode 100644 ui/app/pages/settings/connections-tab/connected-site-row/index.js create mode 100644 ui/app/pages/settings/connections-tab/connected-site-row/index.scss create mode 100644 ui/app/pages/settings/connections-tab/connections-tab.component.js create mode 100644 ui/app/pages/settings/connections-tab/connections-tab.container.js create mode 100644 ui/app/pages/settings/connections-tab/index.js create mode 100644 ui/app/pages/settings/connections-tab/index.scss diff --git a/.circleci/config.yml b/.circleci/config.yml index 5270da98f..af3b70ebb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -22,7 +22,6 @@ workflows: - test-lint: requires: - prep-deps - - test-lint-shellcheck - test-e2e-chrome: requires: - prep-deps @@ -50,7 +49,6 @@ workflows: - all-tests-pass: requires: - test-lint - - test-lint-shellcheck - test-unit - test-unit-global - test-mozilla-lint @@ -175,16 +173,6 @@ jobs: name: Verify locales command: yarn verify-locales --quiet - test-lint-shellcheck: - docker: - - image: circleci/node:10.17-browsers - steps: - - checkout - - run: sudo apt-get install shellcheck - - run: - name: Shellcheck Lint - command: yarn lint:shellcheck - test-deps: docker: - image: circleci/node:10.17-browsers diff --git a/.eslintrc b/.eslintrc index 74b30dcbe..08d8aeb9d 100644 --- a/.eslintrc +++ b/.eslintrc @@ -44,19 +44,18 @@ }, "rules": { - "default-case": 2, "import/no-unresolved": ["error", { "commonjs": true }], "no-restricted-globals": ["error", "event"], "accessor-pairs": 2, "arrow-spacing": [2, { "before": true, "after": true }], "block-spacing": [2, "always"], - "brace-style": 2, + "brace-style": [2, "1tbs", { "allowSingleLine": true }], "camelcase": [2, { "properties": "never" }], "comma-dangle": [2, "always-multiline"], "comma-spacing": [2, { "before": false, "after": true }], "comma-style": [2, "last"], "constructor-super": 2, - "curly": 2, + "curly": [2, "multi-line"], "dot-location": [2, "property"], "eol-last": 2, "eqeqeq": [2, "allow-null"], @@ -143,38 +142,17 @@ "no-useless-computed-key": 2, "no-useless-constructor": 2, "no-useless-escape": 2, - "no-var": 2, "no-whitespace-before-property": 2, "no-with": 2, "one-var": [2, { "initialized": "never" }], "operator-linebreak": [2, "after", { "overrides": { "?": "ignore", ":": "ignore" } }], "padded-blocks": "off", "quotes": [2, "single", {"avoidEscape": true, "allowTemplateLiterals": true}], - "react/no-unused-state": 2, "react/jsx-boolean-value": 2, "react/jsx-curly-brace-presence": [2, { "props": "never", "children": "never" }], "react/jsx-equals-spacing": 2, "react/no-deprecated": 0, "react/default-props-match-prop-types": 2, - "react/jsx-closing-tag-location": 2, - "react/jsx-no-duplicate-props": 2, - "react/jsx-closing-bracket-location": 2, - "react/jsx-first-prop-new-line": 2, - "react/jsx-max-props-per-line": [2, { "maximum": 1, "when": "multiline"} ], - "react/jsx-tag-spacing": [2, { - "closingSlash": "never", - "beforeSelfClosing": "always", - "afterOpening": "never" - }], - "react/jsx-wrap-multilines": [2, { - "declaration": "parens-new-line", - "assignment": "parens-new-line", - "return": "parens-new-line", - "arrow": "parens-new-line", - "condition": "parens-new-line", - "logical": "parens-new-line", - "prop": "parens-new-line" - }], "semi": [2, "never"], "semi-spacing": [2, { "before": false, "after": true }], "space-before-blocks": [2, "always"], diff --git a/.gitattributes b/.gitattributes index 561741e37..590ac71c0 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,3 @@ -* text=auto CHANGELOG.md merge=union # Reviewing the lockfile contents is an important step in verifying that diff --git a/CHANGELOG.md b/CHANGELOG.md index 65b25c13d..02d6a0b64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,19 +2,6 @@ ## Current Develop Branch -## 7.7.0 Thu Nov 28 2019 -- [#7004](https://github.com/MetaMask/metamask-extension/pull/7004): Connect distinct accounts per site -- [#7480](https://github.com/MetaMask/metamask-extension/pull/7480): Fixed link on root README.md -- [#7482](https://github.com/MetaMask/metamask-extension/pull/7482): Update Wyre ETH purchase url -- [#7484](https://github.com/MetaMask/metamask-extension/pull/7484): Ensure transactions are shown in the order they are received -- [#7491](https://github.com/MetaMask/metamask-extension/pull/7491): Update gas when token is changed on the send screen -- [#7501](https://github.com/MetaMask/metamask-extension/pull/7501): Fix accessibility of first-time-flow terms checkboxes -- [#7502](https://github.com/MetaMask/metamask-extension/pull/7502): Fix chainId for non standard networks -- [#7579](https://github.com/MetaMask/metamask-extension/pull/7579): Fix timing of DAI migration notifications after dismissal -- [#7519](https://github.com/MetaMask/metamask-extension/pull/7519): Fixing hardware connect error display -- [#7558](https://github.com/MetaMask/metamask-extension/pull/7558): Use localized messages for NotificationModal buttons -- [#7488](https://github.com/MetaMask/metamask-extension/pull/7488): Fix text overlap when expanding transaction - ## 7.6.1 Tue Nov 19 2019 - [#7475](https://github.com/MetaMask/metamask-extension/pull/7475): Add 'Remind Me Later' to the Maker notification - [#7436](https://github.com/MetaMask/metamask-extension/pull/7436): Add additional rpcUrl verification diff --git a/README.md b/README.md index c1db416e3..08d7e3a74 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,6 @@ To write tests that will be run in the browser using QUnit, add your test files - [How to add new networks to the Provider Menu](./docs/adding-new-networks.md) - [How to port MetaMask to a new platform](./docs/porting_to_new_environment.md) - [How to use the TREZOR emulator](./docs/trezor-emulator.md) -- [How to generate a visualization of this repository's development](./development/gource-viz.sh) +- [How to generate a visualization of this repository's development](./docs/development-visualization.md) [1]: http://www.nomnoml.com/#view/%5B%3Cactor%3Euser%5D%0A%0A%5Bmetamask-ui%7C%0A%20%20%20%5Btools%7C%0A%20%20%20%20%20react%0A%20%20%20%20%20redux%0A%20%20%20%20%20thunk%0A%20%20%20%20%20ethUtils%0A%20%20%20%20%20jazzicon%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20account-detail%0A%20%20%20%20%20accounts%0A%20%20%20%20%20locked-screen%0A%20%20%20%20%20restore-vault%0A%20%20%20%20%20identicon%0A%20%20%20%20%20config%0A%20%20%20%20%20info%0A%20%20%20%5D%0A%20%20%20%5Breducers%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20metamask%0A%20%20%20%20%20identities%0A%20%20%20%5D%0A%20%20%20%5Bactions%7C%0A%20%20%20%20%20%5BbackgroundConnection%5D%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%5D%3A-%3E%5Bactions%5D%0A%20%20%20%5Bactions%5D%3A-%3E%5Breducers%5D%0A%20%20%20%5Breducers%5D%3A-%3E%5Bcomponents%5D%0A%5D%0A%0A%5Bweb%20dapp%7C%0A%20%20%5Bui%20code%5D%0A%20%20%5Bweb3%5D%0A%20%20%5Bmetamask-inpage%5D%0A%20%20%0A%20%20%5B%3Cactor%3Eui%20developer%5D%0A%20%20%5Bui%20developer%5D-%3E%5Bui%20code%5D%0A%20%20%5Bui%20code%5D%3C-%3E%5Bweb3%5D%0A%20%20%5Bweb3%5D%3C-%3E%5Bmetamask-inpage%5D%0A%5D%0A%0A%5Bmetamask-background%7C%0A%20%20%5Bprovider-engine%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bid%20store%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%3E%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%3C-%3E%5Bid%20store%5D%0A%20%20%5Bconfig%20manager%7C%0A%20%20%20%20%5Brpc%20configuration%5D%0A%20%20%20%20%5Bencrypted%20keys%5D%0A%20%20%20%20%5Bwallet%20nicknames%5D%0A%20%20%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%5Bconfig%20manager%5D%0A%20%20%5Bid%20store%5D%3C-%3E%5Bconfig%20manager%5D%0A%5D%0A%0A%5Buser%5D%3C-%3E%5Bmetamask-ui%5D%0A%0A%5Buser%5D%3C%3A--%3A%3E%5Bweb%20dapp%5D%0A%0A%5Bmetamask-contentscript%7C%0A%20%20%5Bplugin%20restart%20detector%5D%0A%20%20%5Brpc%20passthrough%5D%0A%5D%0A%0A%5Brpc%20%7C%0A%20%20%5Bethereum%20blockchain%20%7C%0A%20%20%20%20%5Bcontracts%5D%0A%20%20%20%20%5Baccounts%5D%0A%20%20%5D%0A%5D%0A%0A%5Bweb%20dapp%5D%3C%3A--%3A%3E%5Bmetamask-contentscript%5D%0A%5Bmetamask-contentscript%5D%3C-%3E%5Bmetamask-background%5D%0A%5Bmetamask-background%5D%3C-%3E%5Bmetamask-ui%5D%0A%5Bmetamask-background%5D%3C-%3E%5Brpc%5D%0A diff --git a/app/_locales/am/messages.json b/app/_locales/am/messages.json index 1b9026d4c..08b237aa0 100644 --- a/app/_locales/am/messages.json +++ b/app/_locales/am/messages.json @@ -1,16 +1,28 @@ { + "privacyModeDefault": { + "message": "የግላዊነት ኩነት አሁን በንቡር ነቅቷል" + }, "chartOnlyAvailableEth": { "message": "ቻርት የሚገኘው በ Ethereum አውታረ መረቦች ላይ ብቻ ነው።" }, + "confirmClear": { + "message": "የተፈቀዱ ድረ ገጾችን ለማጥራት እንደሚፈልጉ እርግጠኛ ነዎት?" + }, "contractInteraction": { "message": "የግንኙነት ተግባቦት" }, + "clearApprovalData": { + "message": "የግላዊነት ውሂብን አጥራ" + }, "reject": { "message": "አይቀበሉ" }, - "likeToConnect": { + "providerRequest": { "message": "$1ከመለያዎ ጋር ለመገናኘት ይፈልጋል" }, + "providerRequestInfo": { + "message": "ይህ ድረ ገጽ የእርስዎን መለያ ወቅታዊ አድራሻ ለማየት እየጠየቀ ነው። ምንጊዜም ግንኙነት የሚያደርጉባቸውን ድረ ገጾች የሚያምኗቸው መሆኑን ያረጋግጡ።" + }, "about": { "message": "ስለ" }, @@ -145,6 +157,10 @@ "basic": { "message": "መሠረታዊ" }, + "betweenMinAndMax": { + "message": "ከ$1መብለጥ ወይም እኩል እና ከ$2በታች ወይም እኩል መሆን አለበት።", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerUrl": { "message": "ኤክስፕሎረር አግድ" }, @@ -236,6 +252,9 @@ "connect": { "message": "ይገናኙ" }, + "connectRequest": { + "message": "የግንኙነት ጥያቄ" + }, "connectingTo": { "message": "ከ $1ጋር መገናኘት" }, @@ -275,6 +294,9 @@ "copiedExclamation": { "message": "ተቀድቷል" }, + "copy": { + "message": "ቅዳ" + }, "copyAddress": { "message": "አድራሻን ወደ ቅንጥብ ሰሌዳ ቅዳ" }, @@ -353,6 +375,9 @@ "directDepositEtherExplainer": { "message": "ቀደም ሲል የተወሰነ Ether ካለዎት፣ በአዲሱ ቋትዎ Ether ለማግኘት ፈጣኑ መንገድ ቀጥተኛ ተቀማጭ ነው።" }, + "dismiss": { + "message": "አሰናብት" + }, "done": { "message": "ተጠናቅቋል" }, @@ -518,6 +543,10 @@ "getStarted": { "message": "አስጀማሪ መመሪያ" }, + "greaterThanMin": { + "message": "ከ $1ጋር እኩል መሆን ወይም መብለጥ አለበት።", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "እርስዎን በማየታችን ደስተኛ ነን።" }, @@ -636,6 +665,10 @@ "ledgerAccountRestriction": { "message": "አዲስ መለያ ከማከልዎ በፊት የመጨረሻውን መለያዎን መጠቀም አለብዎት።" }, + "lessThanMax": { + "message": "ከ $1ያነሰ ወይም እኩል መሆን አለበት።", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "አዎ፣ እናደራጅ!" }, @@ -834,6 +867,9 @@ "message": "የግል ቁልፍዎን ሕብረ ቁምፊ እዚህ ለጥፍ፡", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "የዘር ሐረግዎን እዚህ ይለጥፉ!" + }, "pending": { "message": "በእንጥልጥል ላይ ያለ" }, @@ -1147,6 +1183,9 @@ "storePhrase": { "message": "ይህን ሐረግ እንደ 1Password ባለ የይለፍ ቃል አስተዳዳሪ ውስጥ ያስቀምጡ።" }, + "submit": { + "message": "አስገባ" + }, "submitted": { "message": "የቀረበ" }, @@ -1314,6 +1353,9 @@ "userName": { "message": "የተጣቃሚ ስም" }, + "validFileImport": { + "message": "የሚያስመጡትን ትክክለኛ ፋይል መምረጥ አለብዎ፡" + }, "viewAccount": { "message": "መለያን ይመልከቱ" }, diff --git a/app/_locales/ar/messages.json b/app/_locales/ar/messages.json index e686d498c..18b9eafa2 100644 --- a/app/_locales/ar/messages.json +++ b/app/_locales/ar/messages.json @@ -1,16 +1,28 @@ { + "privacyModeDefault": { + "message": "يتم تمكين وضع الخصوصية الآن بشكل افتراضي" + }, "chartOnlyAvailableEth": { "message": "الرسم البياني متاح فقط على شبكات إيثيريوم." }, + "confirmClear": { + "message": "هل أنت متأكد من أنك تريد مسح المواقع المعتمدة؟" + }, "contractInteraction": { "message": "التفاعل على العقد" }, + "clearApprovalData": { + "message": "مسح بيانات الخصوصية" + }, "reject": { "message": "رفض" }, - "likeToConnect": { + "providerRequest": { "message": "يرغب $1 في الاتصال بحسابك" }, + "providerRequestInfo": { + "message": "يطلب هذا الموقع حق الوصول لعرض عنوان حسابك الحالي. تأكد دائماً من ثقتك في المواقع التي تتفاعل معها." + }, "about": { "message": "حول" }, @@ -145,6 +157,10 @@ "basic": { "message": "الأساسية" }, + "betweenMinAndMax": { + "message": "يجب أن تكون أكبر من أو تساوي $1 وأقل من أو تساوي $2.", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerUrl": { "message": "متصفح Block Explorer" }, @@ -236,6 +252,9 @@ "connect": { "message": "اتصال" }, + "connectRequest": { + "message": "طلب اتصال" + }, "connectingTo": { "message": "جارِ الاتصال بـ $1" }, @@ -275,6 +294,9 @@ "copiedExclamation": { "message": "تم النسخ." }, + "copy": { + "message": "نسخ" + }, "copyAddress": { "message": "نسخ العنوان إلى الحافظة" }, @@ -353,6 +375,9 @@ "directDepositEtherExplainer": { "message": "إذا كان لديك بالفعل بعض الأثير، فإن أسرع طريقة للحصول على الأثير في محفظتك الجديدة عن طريق الإيداع المباشر." }, + "dismiss": { + "message": "رفض" + }, "done": { "message": "تم" }, @@ -514,6 +539,10 @@ "getStarted": { "message": "البدء" }, + "greaterThanMin": { + "message": "يجب أن يكون أكبر من أو يساوي $1.", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "نحن سعداء برؤيتك." }, @@ -629,6 +658,10 @@ "ledgerAccountRestriction": { "message": "أنت بحاجة إلى استخدام حسابك الأخير قبل أن تتمكن من إضافة حساب جديد." }, + "lessThanMax": { + "message": "يجب أن يكون أقل من أو يساوي $1.", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "نعم، دعنا نبدأ التثبيت!" }, @@ -830,6 +863,9 @@ "message": "الصق مقطع مفتاحك الخاص هنا:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "قم بلصق عبارة الأمان الخاصة بك هنا!" + }, "pending": { "message": "قيد الانتظار" }, @@ -1143,6 +1179,9 @@ "storePhrase": { "message": "احتفظ بهذه الجملة في مدير كلمات مرور مثل 1Password." }, + "submit": { + "message": "إرسال" + }, "submitted": { "message": "المقدمة" }, @@ -1310,6 +1349,9 @@ "userName": { "message": "اسم المستخدم" }, + "validFileImport": { + "message": "يجب عليك تحديد ملف صالح للاستيراد." + }, "viewAccount": { "message": "عرض حساب" }, diff --git a/app/_locales/bg/messages.json b/app/_locales/bg/messages.json index 249e1c779..799214110 100644 --- a/app/_locales/bg/messages.json +++ b/app/_locales/bg/messages.json @@ -1,21 +1,33 @@ { + "privacyModeDefault": { + "message": "Режимът на поверителност вече е активиран по подразбиране" + }, "chartOnlyAvailableEth": { "message": "Диаграмата е достъпна само в мрежи на Ethereum." }, + "confirmClear": { + "message": "Сигурни ли сте, че искате да изчистите одобрените уебсайтове?" + }, "contractInteraction": { "message": "Взаимодействие с договор" }, + "clearApprovalData": { + "message": "Изчистване на данните за поверителност" + }, "reject": { "message": "Отхвърляне" }, - "likeToConnect": { + "providerRequest": { "message": "$1 би искал да се свърже с вашия акаунт" }, + "providerRequestInfo": { + "message": "Този сайт иска достъп за преглед на адреса на текущия ви акаунт. Винаги се уверявайте, че се доверявате на сайтовете, с които взаимодействате." + }, "about": { "message": "Информация" }, "aboutSettingsDescription": { - "message": "Версия, център за поддръжка и информация за контакт" + "message": "Версия, център за поддръжка и информация за контакт." }, "acceleratingATransaction": { "message": "* Ускоряването на транзакция чрез използване на по-висока цена на газа увеличава шансовете й да се обработва по-бързо от мрежата, но това не винаги е гарантирано." @@ -51,7 +63,7 @@ "message": "Разширени" }, "advancedSettingsDescription": { - "message": "Достъп до функции за разработчици, изтегляйте дневници, нулиране на акаунта, тестови мрежи за настройка и персонализиран RPC" + "message": "Достъп до функции за разработчици, изтегляйте дневници, нулиране на акаунта, тестови мрежи за настройка и персонализиран RPC." }, "advancedOptions": { "message": "Разширени опции" @@ -145,6 +157,10 @@ "basic": { "message": "Основни" }, + "betweenMinAndMax": { + "message": "трябва да бъде по-голям или равен на $1 и по-малък или равен на $2.", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerUrl": { "message": "Блокиране на Explorer" }, @@ -236,6 +252,9 @@ "connect": { "message": "Свързване" }, + "connectRequest": { + "message": "Свържете заявка" + }, "connectingTo": { "message": "Свързване с $1" }, @@ -275,6 +294,9 @@ "copiedExclamation": { "message": "Копирано!" }, + "copy": { + "message": "Копиране" + }, "copyAddress": { "message": "Копирайте адреса в клипборда" }, @@ -353,6 +375,9 @@ "directDepositEtherExplainer": { "message": "Ако вече имате някакъв етер, най-бързият начин да получите етер в новия си портфейл е чрез директен депозит." }, + "dismiss": { + "message": "Отхвърляне" + }, "done": { "message": "Готово" }, @@ -514,6 +539,10 @@ "getStarted": { "message": "Първи стъпки" }, + "greaterThanMin": { + "message": "трябва да бъде по-голяма или равна на $1.", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "Радваме се да Ви видим." }, @@ -632,6 +661,10 @@ "ledgerAccountRestriction": { "message": "Трябва да използвате последния си акаунт, преди да можете да добавите нов." }, + "lessThanMax": { + "message": "трябва да бъде по-малко или равно на $1.", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "Да, нека да настроим нещата!" }, @@ -833,6 +866,9 @@ "message": "Поставете низ от личния си ключ тук:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Поставете тук ключовата си фраза!" + }, "pending": { "message": "в изчакване" }, @@ -1146,6 +1182,9 @@ "storePhrase": { "message": "Съхранявайте тази фраза в мениджър на пароли като 1Password." }, + "submit": { + "message": "Изпращане" + }, "submitted": { "message": "Изпратен" }, @@ -1313,6 +1352,9 @@ "userName": { "message": "Потребителско име" }, + "validFileImport": { + "message": "Трябва да изберете валиден файл, който да импортирате." + }, "viewAccount": { "message": "Преглед на профила" }, diff --git a/app/_locales/bn/messages.json b/app/_locales/bn/messages.json index ae36440da..de1c0b6da 100644 --- a/app/_locales/bn/messages.json +++ b/app/_locales/bn/messages.json @@ -1,16 +1,28 @@ { + "privacyModeDefault": { + "message": "গোপনীয়তার মোড এখন ডিফল্ট হিসাবে সক্রিয় করা আছে" + }, "chartOnlyAvailableEth": { "message": "শুধুমাত্র Ethereum নেটওয়ার্কগুলিতে চার্ট উপলভ্য। " }, + "confirmClear": { + "message": "আপনি কি অনুমোদিত ওয়েবসাইটগুলি মুছে পরিস্কার করার বিষয়ে নিশ্চিত?" + }, "contractInteraction": { "message": "কন্ট্র্যাক্ট বাক্যালাপ" }, + "clearApprovalData": { + "message": "গোপনীয়তার ডেটা মুছে পরিস্কার করুন" + }, "reject": { "message": "প্রত্যাখ্যান" }, - "likeToConnect": { + "providerRequest": { "message": "$1 আপনার অ্যাকাউন্টের সাথে সংযোগ করতে চায়" }, + "providerRequestInfo": { + "message": "এই সাইটটি আপনার বর্তমান অ্যাকাউন্টের ঠিকানা দেখার অ্যাক্সেসের জন্য অনুরোধ জানাচ্ছে। সবসময় নিশ্চিত হয়ে নেবেন যে আপনি যে সাইটের সাথে যোগাযোগ করছেন সেটি বিশ্বাসযোগ্য কিনা।" + }, "about": { "message": "সম্পর্কে" }, @@ -145,6 +157,10 @@ "basic": { "message": "প্রাথমিক" }, + "betweenMinAndMax": { + "message": "অবশ্যই $1 এর বড় বা সমান এবং $2 এর ছোটো বা সমান হতে হবে।", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerUrl": { "message": "এক্সপ্লোরার ব্লক করুন" }, @@ -236,6 +252,9 @@ "connect": { "message": "সংযুক্ত করুন" }, + "connectRequest": { + "message": "সংযোগের অনুরোধ" + }, "connectingTo": { "message": " $1 এর সাথে সংযোগ করছে" }, @@ -275,6 +294,9 @@ "copiedExclamation": { "message": "কপি করা হয়েছে!" }, + "copy": { + "message": "অনুলিপি" + }, "copyAddress": { "message": "ক্লিপবোর্ডে ঠিকানা কপি করুন" }, @@ -353,6 +375,9 @@ "directDepositEtherExplainer": { "message": "আপনার ইতিমধ্যে কিছু ইথার থেকে থাকলে আপনার নতুন ওয়ালেটে ইথার পাওয়ার দ্রুততম উপায় হল সরাসরি জমা করা।" }, + "dismiss": { + "message": "খারিজ" + }, "done": { "message": "সম্পন্ন " }, @@ -518,6 +543,10 @@ "getStarted": { "message": "শুরু করুন" }, + "greaterThanMin": { + "message": "অবশ্যই $1 এর থেকে বড় বা সমান হতে হবে।", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "আপনাকে দেখে আমরা আনন্দিত।" }, @@ -636,6 +665,10 @@ "ledgerAccountRestriction": { "message": "একটি নতুন অ্যা কাউন্ট যোগ করার আগে আপনাকে আপনার শেষ অ্যাকাউন্ট ব্যবহার করে ফেলতে হবে।" }, + "lessThanMax": { + "message": "অবশ্যই $1 এর থেকে কম বা সমান হতে হবে।", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "হ্যাঁ, তাহলে সেট আপ করে নেওয়া যাক!" }, @@ -837,6 +870,9 @@ "message": "আপনার গোপনীয় কী স্ট্রিং এখানে পেস্ট করুন:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "আপনার সীড ফ্রেজ এখানে পেস্ট করুন!" + }, "pending": { "message": "বাকি" }, @@ -1150,6 +1186,9 @@ "storePhrase": { "message": "এই বাক্যাংশটি 1Password এর মতো একটি পাসওয়ার্ড পরিচালকে সংরক্ষণ করুন। " }, + "submit": { + "message": "জমা দিন" + }, "submitted": { "message": "জমা করা হয়েছে" }, @@ -1317,6 +1356,9 @@ "userName": { "message": "ইউজারনেম" }, + "validFileImport": { + "message": "আমদানি করার জন্য আপনাকে অবশ্যই একটি বৈধ ফাইল নির্বাচন করতে হবে।" + }, "viewAccount": { "message": "আ্যাকাউন্ট দেখুন" }, diff --git a/app/_locales/ca/messages.json b/app/_locales/ca/messages.json index 4c131ef09..599cb84ad 100644 --- a/app/_locales/ca/messages.json +++ b/app/_locales/ca/messages.json @@ -1,21 +1,33 @@ { + "privacyModeDefault": { + "message": "El mode de privacitat ara està activat per defecte" + }, "chartOnlyAvailableEth": { "message": "Mostra només els disponibles a les xarxes Ethereum." }, + "confirmClear": { + "message": "Estàs segur que vols eliminar totes les pàgines web aprovades?" + }, "contractInteraction": { "message": "Contractar Interacció" }, + "clearApprovalData": { + "message": "Elimina les dades de privacitat" + }, "reject": { "message": "Rebutja" }, - "likeToConnect": { + "providerRequest": { "message": "a $1 li agradaria connectar-se al teu compte" }, + "providerRequestInfo": { + "message": "Aquesta pàgina està demanant accès a la teva adreça" + }, "about": { "message": "Informació" }, "aboutSettingsDescription": { - "message": "Versió, centre de suport, i informació de contacte" + "message": "Versió, centre de suport, i informació de contacte." }, "acceleratingATransaction": { "message": "* Accelerar una transacció utilitzant un preu de gas més alt augmenta les possibilitats de ser processat més ràpidament per la xarxa, però no sempre es pot garantir." @@ -51,7 +63,7 @@ "message": "Configuració avançada" }, "advancedSettingsDescription": { - "message": "Accedeix a característiques de desenvolupador, descarrega Registres d'Estat, Reinicia el Compte, instal·la testnets i personalitza RPC" + "message": "Accedeix a característiques de desenvolupador, descarrega Registres d'Estat, Reinicia el Compte, instal·la testnets i personalitza RPC." }, "advancedOptions": { "message": "Opcions Avançades" @@ -145,6 +157,10 @@ "basic": { "message": "Opcions bàsiques" }, + "betweenMinAndMax": { + "message": "ha de ser igual o superior a $1 i menor o igual a $2.", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerUrl": { "message": "Bloqueja l'explorador" }, @@ -233,6 +249,9 @@ "connect": { "message": "Connecta" }, + "connectRequest": { + "message": "Sol·licitud de connexió" + }, "connectingTo": { "message": "Connectant a $1 " }, @@ -272,6 +291,9 @@ "copiedExclamation": { "message": "S'ha copiat!" }, + "copy": { + "message": "Copia" + }, "copyAddress": { "message": "Copiar adreça al porta-retalls" }, @@ -350,6 +372,9 @@ "directDepositEtherExplainer": { "message": "Si ja tens una mica d'Ether, la manera més ràpida de posar Ether al teu nou moneder és per dipòsit directe." }, + "dismiss": { + "message": "Omet" + }, "done": { "message": "Fet" }, @@ -508,6 +533,10 @@ "getStarted": { "message": "Comença" }, + "greaterThanMin": { + "message": "ha de ser major o igual a $1.", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "Ens alegrem de veure't." }, @@ -620,6 +649,10 @@ "ledgerAccountRestriction": { "message": "Has de fer servir el teu últim compte abans de poder afegir-ne un altre." }, + "lessThanMax": { + "message": "ha de ser igual o menor a $1 .", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "Sí, posem-nos en marxa!" }, @@ -669,7 +702,7 @@ "message": "Conectant-te a Ethereum i la web descentralitzada." }, "metamaskSeedWords": { - "message": "Frase de recuperació de MetaMask" + "message": "Frase de recuperació de Metamask" }, "metamaskVersion": { "message": "Versió MetaMask" @@ -821,6 +854,9 @@ "message": "Enganxa la teva cadena clau privada aquí:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Enganxa la teva frase de seeds aquí!" + }, "pending": { "message": "pendent" }, @@ -1128,6 +1164,9 @@ "storePhrase": { "message": "Guarda aquesta frase a un gestor de contrasenyes com Contrasenya 1" }, + "submit": { + "message": "Envia" + }, "submitted": { "message": "Enviat" }, @@ -1286,6 +1325,9 @@ "userName": { "message": "Nom d'usuari" }, + "validFileImport": { + "message": "Has de seleccionar un arxiu vàlid per a importar." + }, "viewAccount": { "message": "Mostra el compte" }, diff --git a/app/_locales/cs/messages.json b/app/_locales/cs/messages.json index 9d04ae048..cd768d4d8 100644 --- a/app/_locales/cs/messages.json +++ b/app/_locales/cs/messages.json @@ -1,7 +1,16 @@ { + "confirmClear": { + "message": "Naozaj chcete vymazať schválené webové stránky?" + }, + "clearApprovalData": { + "message": "Jasné údaje o schválení" + }, "reject": { "message": "Odmítnout" }, + "providerRequestInfo": { + "message": "Níže uvedená doména se pokouší požádat o přístup k API Ethereum, aby mohla komunikovat s blokádou Ethereum. Před schválením přístupu Ethereum vždy zkontrolujte, zda jste na správném místě." + }, "account": { "message": "Účet" }, @@ -49,6 +58,10 @@ "balanceIsInsufficientGas": { "message": "Nedostatek prostředků pro aktuální množství paliva" }, + "betweenMinAndMax": { + "message": "musí být větší nebo roven $1 a menší nebo roven $2.", + "description": "helper for inputting hex as decimal input" + }, "blockiesIdenticon": { "message": "Použít Blockies Identicon" }, @@ -100,6 +113,9 @@ "copiedExclamation": { "message": "Zkopírováno!" }, + "copy": { + "message": "Kopírovat" + }, "copyToClipboard": { "message": "Kopírovat do schránky" }, @@ -211,6 +227,10 @@ "message": "Získejte Ether z faucetu za $1.", "description": "Displays network name for Ether faucet" }, + "greaterThanMin": { + "message": "musí být větší nebo roven $1.", + "description": "helper for inputting hex as decimal input" + }, "here": { "message": "zde", "description": "as in -click here- for more information (goes with troubleTokenBalances)" @@ -265,6 +285,10 @@ "learnMore": { "message": "Zjistěte více." }, + "lessThanMax": { + "message": "musí být menší nebo roven $1.", + "description": "helper for inputting hex as decimal input" + }, "likeToAddTokens": { "message": "Chcete přidat tyto tokeny?" }, @@ -343,6 +367,9 @@ "message": "Vložte zde svůj privátní klíč:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Svou klíčovou frázi vložte zde!" + }, "personalAddressDetected": { "message": "Detekována osobní adresa. Zadejte adresu kontraktu tokenu." }, @@ -449,6 +476,9 @@ "stateLogError": { "message": "Chyba během získávání stavových protokolů." }, + "submit": { + "message": "Odeslat" + }, "submitted": { "message": "Odesláno" }, @@ -505,6 +535,9 @@ "usedByClients": { "message": "Používána různými klienty" }, + "validFileImport": { + "message": "Musíte vybrat validní soubor k importu." + }, "viewAccount": { "message": "Zobrazit účet" }, diff --git a/app/_locales/da/messages.json b/app/_locales/da/messages.json index 1c5fa4edb..5a2c3c100 100644 --- a/app/_locales/da/messages.json +++ b/app/_locales/da/messages.json @@ -1,21 +1,33 @@ { + "privacyModeDefault": { + "message": "Privatlivstilstand er nu som udgangspunkt aktiveret" + }, "chartOnlyAvailableEth": { "message": "Skema kun tilgængeligt på Ethereum-netværk." }, + "confirmClear": { + "message": "Er du sikker på, at du vil rydde godkendte hjemmesider?" + }, "contractInteraction": { "message": "Kontraktinteraktion" }, + "clearApprovalData": { + "message": "Ryd fortrolighedsdata" + }, "reject": { "message": "Afvis" }, - "likeToConnect": { + "providerRequest": { "message": "$1 ønsker at forbinde til din konto" }, + "providerRequestInfo": { + "message": "Denne side anmoder om at se din nuværende kontoadresse. Sørg altid for, at du stoler på de sider du interagerer med." + }, "about": { "message": "Om" }, "aboutSettingsDescription": { - "message": "Version, supportcenter og kontaktinformation" + "message": "Version, supportcenter og kontaktinformation." }, "acceleratingATransaction": { "message": "* At gøre din transaktion hurtigere ved at bruge en højere Gas-priser, øger dennes chancer for at blive behandlet af netværket hurtigere, men det er ikke altid garanteret." @@ -51,7 +63,7 @@ "message": "Avanceret" }, "advancedSettingsDescription": { - "message": "Få adgang til udviklerfunktioner, hent tilstandslogs, nulstil konto, opsæt testnetværk og brugerdefineret RPC" + "message": "Få adgang til udviklerfunktioner, hent tilstandslogs, nulstil konto, opsæt testnetværk og brugerdefineret RPC." }, "advancedOptions": { "message": "Avancerede Valgmuligheder" @@ -145,6 +157,10 @@ "basic": { "message": "Grundlæggende oplysninger" }, + "betweenMinAndMax": { + "message": "skal være større end eller lig med $1 og mindre end eller lig med $2.", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerUrl": { "message": "Block-udforsker" }, @@ -236,6 +252,9 @@ "connect": { "message": "Få forbindelse" }, + "connectRequest": { + "message": "Tilslutningsanmodning" + }, "connectingTo": { "message": "Forbinder til $1" }, @@ -275,6 +294,9 @@ "copiedExclamation": { "message": "Kopieret!" }, + "copy": { + "message": "Kopiér" + }, "copyAddress": { "message": "Kopier adresse til udklipsholder" }, @@ -353,6 +375,9 @@ "directDepositEtherExplainer": { "message": "Hvis du allerede har Ether, er den hurtigste måde at få Ether i din nye tegnebog ved direkte indbetaling." }, + "dismiss": { + "message": "Luk" + }, "done": { "message": "Færdig" }, @@ -514,6 +539,10 @@ "getStarted": { "message": "Kom godt i gang" }, + "greaterThanMin": { + "message": "skal være større end eller lig med $1.", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "Vi er glade for at se dig." }, @@ -629,6 +658,10 @@ "ledgerAccountRestriction": { "message": "Du skal benytte din tidligere konto, før du kan tilføje en ny." }, + "lessThanMax": { + "message": "skal være mindre end eller lig med $1.", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "Ja, lad os komme i gang!" }, @@ -815,6 +848,9 @@ "message": "Indsæt din private nøglestreng her:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Indsæt din seed-sætning her!" + }, "pending": { "message": "afventer" }, @@ -1125,6 +1161,9 @@ "storePhrase": { "message": "Gem denne sætning i en adgangskodeadministrator som 1Password." }, + "submit": { + "message": "Indsend" + }, "submitted": { "message": "Indsendt" }, @@ -1283,6 +1322,9 @@ "userName": { "message": "Brugernavn" }, + "validFileImport": { + "message": "Du skal vælge en gyldig fil at importere." + }, "viewAccount": { "message": "Vis konto" }, diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json index f964d0c2d..5f241ab05 100644 --- a/app/_locales/de/messages.json +++ b/app/_locales/de/messages.json @@ -1,21 +1,33 @@ { + "privacyModeDefault": { + "message": "Der Datenschutzmodus ist jetzt standardmäßig aktiviert" + }, "chartOnlyAvailableEth": { "message": "Die Grafik ist nur in Ethereum-Netzwerken verfügbar." }, + "confirmClear": { + "message": "Möchten Sie die genehmigten Websites wirklich löschen?" + }, "contractInteraction": { "message": "Vertragsinteraktion" }, + "clearApprovalData": { + "message": "Genehmigungsdaten löschen" + }, "reject": { "message": "Ablehnen" }, - "likeToConnect": { + "providerRequest": { "message": "$1 möchte sich mit deinem Account verbinden" }, + "providerRequestInfo": { + "message": "Diese Website fordert Zugriff auf Ihre aktuelle Kontoadresse. Stellen Sie immer sicher, dass Sie den Websites vertrauen, mit denen Sie interagieren." + }, "about": { "message": "Über" }, "aboutSettingsDescription": { - "message": "Version, Supportcenter und Kontaktinformationen" + "message": "Version, Supportcenter und Kontaktinformationen." }, "acceleratingATransaction": { "message": "* Die Beschleunigung einer Transaktion durch die Verwendung eines höheren Gaspreises erhöht die Chancen einer schnelleren Verarbeitung durch das Netz, wofür es allerdings keine Garantie gibt." @@ -48,7 +60,7 @@ "message": "Erweitert" }, "advancedSettingsDescription": { - "message": "Zugriff auf Entwicklerfunktionen, Download von Statusprotokollen, Zurücksetzen des Kontos, Einrichten von Testnetzen und benutzerdefinierten RPCs" + "message": "Zugriff auf Entwicklerfunktionen, Download von Statusprotokollen, Zurücksetzen des Kontos, Einrichten von Testnetzen und benutzerdefinierten RPCs." }, "advancedOptions": { "message": "Erweiterte Optionen" @@ -142,6 +154,10 @@ "basic": { "message": "Grundlegend" }, + "betweenMinAndMax": { + "message": "Muss größer oder gleich $1 und kleiner oder gleich $2 sein.", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerUrl": { "message": "Block-Explorer" }, @@ -224,6 +240,9 @@ "connect": { "message": "Verbinden" }, + "connectRequest": { + "message": "Verbindungsanfrage" + }, "connectingTo": { "message": "Verbindung mit $1 wird hergestellt" }, @@ -263,6 +282,9 @@ "copiedExclamation": { "message": "Kopiert!" }, + "copy": { + "message": "Kopieren" + }, "copyAddress": { "message": "Adresse in die Zwischenablage kopieren" }, @@ -338,6 +360,9 @@ "directDepositEtherExplainer": { "message": "Wenn du bereits Ether besitzt, ist die sofortige Einzahlung die schnellste Methode Ether in deine neue Wallet zu bekommen." }, + "dismiss": { + "message": "Schließen" + }, "done": { "message": "Fertig" }, @@ -503,6 +528,10 @@ "getStarted": { "message": "Erste Schritte" }, + "greaterThanMin": { + "message": "Muss größer oder gleich $1 sein.", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "Wir freuen uns, Sie zu sehen." }, @@ -618,6 +647,10 @@ "ledgerAccountRestriction": { "message": "Sie müssen Ihr letztes Konto verwenden, ehe Sie ein neues hinzufügen können." }, + "lessThanMax": { + "message": "Muss kleiner oder gleich $1 sein.", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "Ja, legen wir los!" }, @@ -807,6 +840,9 @@ "message": "Füge deine Private Key Zeichenfolge hier ein:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Füge deine Seed-Wörterfolge hier ein!" + }, "pending": { "message": "ausstehend" }, @@ -1116,6 +1152,9 @@ "storePhrase": { "message": "Speichern Sie diesen Schlüssel in einem Passwortmanager wie 1Password." }, + "submit": { + "message": "Einreichen" + }, "submitted": { "message": "Abgeschickt" }, @@ -1274,6 +1313,9 @@ "userName": { "message": "Nutzername" }, + "validFileImport": { + "message": "Du musst eine gültige Datei für den Import auswählen." + }, "viewAccount": { "message": " Account einsehen" }, diff --git a/app/_locales/el/messages.json b/app/_locales/el/messages.json index 6f35b1447..126b9133f 100644 --- a/app/_locales/el/messages.json +++ b/app/_locales/el/messages.json @@ -1,21 +1,33 @@ { + "privacyModeDefault": { + "message": "Η Λειτουργία Απορρήτου είναι πλέον ενεργοποιημένη από προεπιλογή" + }, "chartOnlyAvailableEth": { "message": "Το διάγραμμα είναι διαθέσιμο μόνο σε δίκτυα Ethereum." }, + "confirmClear": { + "message": "Είστε βέβαιοι ότι θέλετε να διαγράψετε τους εγκεκριμένους ιστότοπους;" + }, "contractInteraction": { "message": "Αλληλεπίδραση Σύμβασης" }, + "clearApprovalData": { + "message": "Εκκαθάριση Δεδομένων Απορρήτου" + }, "reject": { "message": "Απόρριψη" }, - "likeToConnect": { + "providerRequest": { "message": "Αίτημα σύνδεσης στον λογαριασμό σας από $1" }, + "providerRequestInfo": { + "message": "Ο ιστότοπος ζητά πρόσβαση για προβολή της τρέχουσας διεύθυνσης του λογαριασμού σας. Να σιγουρεύεστε πάντα ότι εμπιστεύεστε τους ιστότοπους με τους οποίους αλληλεπιδράτε." + }, "about": { "message": "Σχετικά με" }, "aboutSettingsDescription": { - "message": "Έκδοση, κέντρο υποστήριξης και πληροφορίες επικοινωνίας" + "message": "Έκδοση, κέντρο υποστήριξης και πληροφορίες επικοινωνίας." }, "acceleratingATransaction": { "message": "* Η επιτάχυνση μιας συναλλαγής με τη χρήση υψηλότερης τιμής καυσίμου αυξάνει τις πιθανότητές της για ταχύτερη επεξεργασία από το δίκτυο, αλλά δεν είναι πάντοτε εγγυημένη." @@ -51,7 +63,7 @@ "message": "Σύνθετες" }, "advancedSettingsDescription": { - "message": "Αποκτήστε πρόσβαση στις λειτουργίες του προγραμματιστή, κατεβάστε Αρχεία Καταγραφών Καταστάσεων, Επαναφέρετε τον Λογαριασμό, εγκαταστήστε δοκιμαστικά δίκτυα και προσαρμοσμένα RPC" + "message": "Αποκτήστε πρόσβαση στις λειτουργίες του προγραμματιστή, κατεβάστε Αρχεία Καταγραφών Καταστάσεων, Επαναφέρετε τον Λογαριασμό, εγκαταστήστε δοκιμαστικά δίκτυα και προσαρμοσμένα RPC." }, "advancedOptions": { "message": "Σύνθετες Επιλογές" @@ -145,6 +157,10 @@ "basic": { "message": "Βασικά" }, + "betweenMinAndMax": { + "message": "πρέπει να είναι μεγαλύτερο ή ίσο με $1 και μικρότερο ή ίσο με $2.", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerUrl": { "message": "Εξερευνητής Μπλοκ" }, @@ -233,6 +249,9 @@ "connect": { "message": "Σύνδεση" }, + "connectRequest": { + "message": "Αίτημα Σύνδεσης" + }, "connectingTo": { "message": "Σύνδεση με $1" }, @@ -272,6 +291,9 @@ "copiedExclamation": { "message": "Έγινε αντιγραφή!" }, + "copy": { + "message": "Αντιγραφή" + }, "copyAddress": { "message": "Αντιγράψτε τη διεύθυνση στο πρόχειρο" }, @@ -350,6 +372,9 @@ "directDepositEtherExplainer": { "message": "Αν έχετε ήδη κάποια Ether, ο πιο γρήγορος τρόπος για να πάρετε τα Ether στο νέο σας πορτοφόλι με άμεση κατάθεση." }, + "dismiss": { + "message": "Παράβλεψη" + }, "done": { "message": "Τέλος" }, @@ -515,6 +540,10 @@ "getStarted": { "message": "Έναρξη" }, + "greaterThanMin": { + "message": "πρέπει να είναι μεγαλύτερο ή ίσο με $1.", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "Χαιρόμαστε που σας βλέπουμε." }, @@ -633,6 +662,10 @@ "ledgerAccountRestriction": { "message": "Πρέπει να χρησιμοποιήσετε τον τελευταίο σας λογαριασμό πριν προσθέσετε έναν νέο." }, + "lessThanMax": { + "message": "πρέπει να είναι μικρότερο από ή ίσο με $1.", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "Ναι, ας το εγκαταστήσουμε!" }, @@ -834,6 +867,9 @@ "message": "Επικολλήστε τη συμβολοσειρά ιδιωτικού κλειδιού εδώ:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Επικολλήστε τη φράση επαναφοράς σας εδώ!" + }, "pending": { "message": "σε εκκρεμότητα" }, @@ -1147,6 +1183,9 @@ "storePhrase": { "message": "Αποθηκεύστε αυτήν τη φράση σε έναν διαχειριστή κωδικών πρόσβασης όπως το 1Password." }, + "submit": { + "message": "Υποβολή" + }, "submitted": { "message": "Υποβλήθηκε" }, @@ -1311,6 +1350,9 @@ "userName": { "message": "Όνομα χρήστη" }, + "validFileImport": { + "message": "Πρέπει να επιλέξετε έναν έγκυρο φάκελο για εισαγωγή." + }, "viewAccount": { "message": "Προβολή λογαριασμού" }, diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index b61349894..f947c9e5c 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -14,39 +14,53 @@ "showIncomingTransactionsDescription": { "message": "Select this to use Etherscan to show incoming transactions in the transactions list" }, - "cancelledConnectionWithMetaMask": { - "message": "Cancelled Connection With MetaMask" - }, "chartOnlyAvailableEth": { "message": "Chart only available on Ethereum networks." }, - "connectedSites": { - "message": "Connected Sites" + "confirmClear": { + "message": "Are you sure you want to clear approved websites?" }, - "connectingWithMetaMask": { - "message": "Connecting With MetaMask..." + "connections": { + "message": "Connections" }, - "connectTo": { - "message": "Connect to $1", - "description": "$1 is the name/origin of a site/dapp that the user can connect to metamask" + "connectionsSettingsDescription": { + "message": "Sites allowed to read your accounts" }, - "chooseAnAcount": { - "message": "Choose an account" + "addSite": { + "message": "Add Site" + }, + "addSiteDescription": { + "message": "Manually add a site to allow it access to your accounts, useful for older dapps" + }, + "connected": { + "message": "Connected" + }, + "connectedDescription": { + "message": "The list of sites allowed access to your addresses" + }, + "privacyModeDefault": { + "message": "Privacy Mode is now enabled by default" }, "contractInteraction": { "message": "Contract Interaction" }, + "clearApprovalData": { + "message": "Remove all sites" + }, "reject": { "message": "Reject" }, - "redirectingBackToDapp": { - "message": "Redirecting back to dapp..." + "providerRequest": { + "message": "$1 would like to connect to your account" + }, + "providerRequestInfo": { + "message": "This site is requesting access to view your current account address. Always make sure you trust the sites you interact with." }, "about": { "message": "About" }, "aboutSettingsDescription": { - "message": "Version, support center, and contact info" + "message": "Version, support center, and contact info." }, "acceleratingATransaction": { "message": "* Accelerating a transaction by using a higher gas price increases its chances of getting processed by the network faster, but it is not always guaranteed." @@ -86,7 +100,7 @@ "message": "Advanced" }, "advancedSettingsDescription": { - "message": "Access developer features, download State Logs, Reset Account, setup testnets and custom RPC" + "message": "Access developer features, download State Logs, Reset Account, setup testnets and custom RPC." }, "advancedOptions": { "message": "Advanced Options" @@ -191,6 +205,10 @@ "basic": { "message": "Basic" }, + "betweenMinAndMax": { + "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" }, @@ -291,6 +309,9 @@ "connect": { "message": "Connect" }, + "connectRequest": { + "message": "Connect Request" + }, "connectingTo": { "message": "Connecting to $1" }, @@ -336,6 +357,9 @@ "copiedExclamation": { "message": "Copied!" }, + "copy": { + "message": "Copy" + }, "copyAddress": { "message": "Copy address to clipboard" }, @@ -414,36 +438,14 @@ "details": { "message": "Details" }, - "disconnectAccount": { - "message": "Disconnect account" - }, - "disconnectAll": { - "message": "Disconnect All" - }, - "disconnectAllModalDescription": { - "message": "Are you sure? You will be disconnected from all sites on all accounts." - }, - "disconnectAccountModalDescription": { - "message": "Are you sure? Your account (\"$1\") will be disconnected from this site." - }, - "disconnectAccountQuestion": { - "message": "Disconnect account?" - }, - "disconnectFromThisAccount": { - "message": "Disconnect from this account?" - }, - "disconnectAllAccountsQuestion": { - "message": "Disconnect all accounts?" - }, "directDepositEther": { "message": "Directly Deposit Ether" }, "directDepositEtherExplainer": { "message": "If you already have some Ether, the quickest way to get Ether in your new wallet by direct deposit." }, - "domainLastConnect": { - "message": "Last Connected: $1", - "description": "$1 is the date at which the user was last connected to a given domain" + "dismiss": { + "message": "Dismiss" }, "done": { "message": "Done" @@ -505,17 +507,6 @@ "endOfFlowMessage10": { "message": "All Done" }, - "extensionId": { - "message": "Extension ID: $1", - "description": "$1 is a string of random letters that are the id of another extension connecting to MetaMask" - }, - "externalExtension": { - "message": "External Extension" - }, - "onboardingReturnNotice": { - "message": "\"$1\" will close this tab and direct back to $2", - "description": "Return the user to the site that initiated onboarding" - }, "ensRegistrationError": { "message": "Error in ENS name registration" }, @@ -633,6 +624,10 @@ "getStarted": { "message": "Get Started" }, + "greaterThanMin": { + "message": "must be greater than or equal to $1.", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "We’re happy to see you." }, @@ -745,28 +740,22 @@ "max": { "message": "Max" }, - "lastConnected": { - "message": "Last Connected" - }, "learnMore": { "message": "Learn more" }, - "learnAboutRisks": { - "message": "Learn about the risks here." - }, "ledgerAccountRestriction": { "message": "You need to make use your last account before you can add a new one." }, + "lessThanMax": { + "message": "must be less than or equal to $1.", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "Yes, let’s get set up!" }, "likeToAddTokens": { "message": "Would you like to add these tokens?" }, - "likeToConnect": { - "message": "$1 would like to connect to your MetaMask account", - "description": "$1 is the name/url of a site/dapp asking to connect to MetaMask" - }, "links": { "message": "Links" }, @@ -898,9 +887,6 @@ "rpcUrl": { "message": "New RPC URL" }, - "onlyConnectTrust": { - "message": "Only connect with sites you trust." - }, "optionalChainId": { "message": "ChainID (optional)" }, @@ -975,6 +961,9 @@ "message": "Paste your private key string here:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Paste your seed phrase here!" + }, "pending": { "message": "pending" }, @@ -1103,9 +1092,6 @@ "readyToConnect": { "message": "Ready to Connect?" }, - "revokeInPermissions": { - "message": "* You can view and revoke permissions in MetaMask settings." - }, "rinkeby": { "message": "Rinkeby Test Network" }, @@ -1362,14 +1348,6 @@ "testFaucet": { "message": "Test Faucet" }, - "thisWillAllow": { - "message": "This will allow $1 to:", - "description": "$1 is the name or domain of a site/dapp that is requesting permissions" - }, - "thisWillAllowExternalExtension": { - "message": "This will allow an external extension with id $1 to:", - "description": "$1 is a string of random letters that are the id of another extension connecting to MetaMask" - }, "thisWillCreate": { "message": "This will create a new wallet and seed phrase" }, @@ -1382,10 +1360,6 @@ "toWithColon": { "message": "To:" }, - "toConnectWith": { - "message": "To connect with $1", - "description": "$1 is the name or domain of a site/dapp that asking to connect with MetaMask" - }, "toETHviaShapeShift": { "message": "$1 to ETH via ShapeShift", "description": "system will fill in deposit type in start of message" @@ -1515,6 +1489,9 @@ "userName": { "message": "Username" }, + "validFileImport": { + "message": "You must select a valid file to import." + }, "viewAccount": { "message": "View Account" }, diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json index f64105f26..e503cd977 100644 --- a/app/_locales/es/messages.json +++ b/app/_locales/es/messages.json @@ -1,16 +1,28 @@ { + "privacyModeDefault": { + "message": "Modo Privado está activado ahora por defecto" + }, "chartOnlyAvailableEth": { "message": "Tabla solo disponible en redes Ethereum." }, + "confirmClear": { + "message": "¿Seguro que quieres borrar los sitios web aprobados?" + }, "contractInteraction": { "message": "Interacción con contrato" }, + "clearApprovalData": { + "message": "Borrar datos de aprobación" + }, "reject": { "message": "Rechazar" }, - "likeToConnect": { + "providerRequest": { "message": "$1 quisiera conectar con tu cuenta" }, + "providerRequestInfo": { + "message": "El dominio que se muestra a continuación intenta solicitar acceso a la API Ethereum para que pueda interactuar con la blockchain de Ethereum. Siempre verifique que esté en el sitio correcto antes de aprobar el acceso Ethereum." + }, "about": { "message": "Acerca" }, @@ -133,6 +145,10 @@ "basic": { "message": "Básico" }, + "betweenMinAndMax": { + "message": "Debe ser mayor o igual a $1 y menor o igual a $2", + "description": "helper for inputting hex as decimal input" + }, "blockiesIdenticon": { "message": "Usar Blockies Identicon (Iconos)" }, @@ -193,6 +209,9 @@ "connect": { "message": "Conectar" }, + "connectRequest": { + "message": "Petición para conectar" + }, "connectingTo": { "message": "Conectánodse a $1" }, @@ -232,6 +251,9 @@ "copiedExclamation": { "message": "¡Copiado!" }, + "copy": { + "message": "Copiar" + }, "copyAddress": { "message": "Copiar la dirección al portapapeles" }, @@ -310,6 +332,9 @@ "directDepositEtherExplainer": { "message": "Si posees Ether, la forma más rápida de transferirlo a tu nueva billetera es depositándolo directamente" }, + "dismiss": { + "message": "Descartar" + }, "done": { "message": "Completo" }, @@ -424,6 +449,10 @@ "getHelp": { "message": "Pedir ayuda." }, + "greaterThanMin": { + "message": "Debe ser mayor o igual a $1", + "description": "helper for inputting hex as decimal input" + }, "hardwareWalletConnected": { "message": "Se ha conectado el monedero físico" }, @@ -524,6 +553,10 @@ "ledgerAccountRestriction": { "message": "Hay que hacer uso de tu última cuenta antes de agregarle una nueva." }, + "lessThanMax": { + "message": "Debe ser menor o igual a $1", + "description": "helper for inputting hex as decimal input" + }, "likeToAddTokens": { "message": "¿Te gustaría agregar estos tokens?" }, @@ -668,6 +701,9 @@ "message": "Pega tu clave privada aqui", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "¡Pega tu frase semilla aquí!" + }, "pending": { "message": "pendiente" }, @@ -930,6 +966,9 @@ "step3HardwareWalletMsg": { "message": "Usa tu cuenta física igual que harías con cualquier cuenta de Ethereum. Regístrate con dApps, manda Eth, compra y almacena tokens de ERC20 y otros tokens no-fungibles, como CryptoKitties." }, + "submit": { + "message": "Enviar" + }, "submitted": { "message": "Enviado" }, @@ -1058,6 +1097,9 @@ "usedByClients": { "message": "Utilizado por una variedad de clientes diferentes" }, + "validFileImport": { + "message": "Debes selecionar un archivo valido para importar" + }, "viewAccount": { "message": "Mirar cuenta" }, diff --git a/app/_locales/es_419/messages.json b/app/_locales/es_419/messages.json index 0645b0eaf..c861be81b 100644 --- a/app/_locales/es_419/messages.json +++ b/app/_locales/es_419/messages.json @@ -1,21 +1,33 @@ { + "privacyModeDefault": { + "message": "El modo de privacidad está habilitado de manera predeterminada" + }, "chartOnlyAvailableEth": { "message": "Chart está disponible únicamente en las redes de Ethereum." }, + "confirmClear": { + "message": "¿Estás seguro de que deseas borrar los sitios web aprobados?" + }, "contractInteraction": { "message": "Interacción contractual" }, + "clearApprovalData": { + "message": "Borrar datos de privacidad" + }, "reject": { "message": "Rechazar" }, - "likeToConnect": { + "providerRequest": { "message": "$1 desea conectarse a tu cuenta" }, + "providerRequestInfo": { + "message": "Este sitio está solicitando acceso para ver la dirección de tu cuenta corriente. Asegúrate siempre de que confías en los sitios con los que interactúas." + }, "about": { "message": "Acerca de" }, "aboutSettingsDescription": { - "message": "Versión, centro de soporte técnico e información de contacto" + "message": "Versión, centro de soporte técnico e información de contacto." }, "acceleratingATransaction": { "message": "* Aumentar una transacción utilizando un precio de gas más alto aumenta sus posibilidades de ser procesada más rápido por la red, pero esto no siempre está garantizado." @@ -51,7 +63,7 @@ "message": "Avanzada" }, "advancedSettingsDescription": { - "message": "Accede a las funciones de desarrollador, descarga los registros de estado, restablece la cuenta, y configura las redes Testnet y el RPC personalizado" + "message": "Accede a las funciones de desarrollador, descarga los registros de estado, restablece la cuenta, y configura las redes Testnet y el RPC personalizado." }, "advancedOptions": { "message": "Opciones avanzadas" @@ -145,6 +157,10 @@ "basic": { "message": "Básicas" }, + "betweenMinAndMax": { + "message": "debe ser superior o igual a $1 e inferior o igual a $2.", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerUrl": { "message": "Bloquear Explorer" }, @@ -233,6 +249,9 @@ "connect": { "message": "Conectar" }, + "connectRequest": { + "message": "Solicitud de conexión" + }, "connectingTo": { "message": "Conexión con $1" }, @@ -272,6 +291,9 @@ "copiedExclamation": { "message": "Copiado" }, + "copy": { + "message": "Copiar" + }, "copyAddress": { "message": "Copiar la dirección al portapapeles" }, @@ -350,6 +372,9 @@ "directDepositEtherExplainer": { "message": "Si ya tienes algunos Ethers, la forma más rápida de ingresarlos en tu nueva billetera es a través de un depósito directo." }, + "dismiss": { + "message": "Rechazar" + }, "done": { "message": "Listo" }, @@ -512,6 +537,10 @@ "getStarted": { "message": "Comenzar" }, + "greaterThanMin": { + "message": "debe ser superior o igual a $1.", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "Estamos encantados de verte." }, @@ -627,6 +656,10 @@ "ledgerAccountRestriction": { "message": "Necesitas utilizar tu cuenta anterior para agregar una nueva." }, + "lessThanMax": { + "message": "debe ser menor o igual a $1.", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "¡Eso, pongámonos en marcha!" }, @@ -822,6 +855,9 @@ "message": "Copia y pega tu cadena de claves privada aquí:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "¡Copia y pega tu frase de inicialización aquí!" + }, "pending": { "message": "pendiente" }, @@ -1135,6 +1171,9 @@ "storePhrase": { "message": "Almacena esta frase en un administrador de contraseñas como 1Password." }, + "submit": { + "message": "Enviar" + }, "submitted": { "message": "Enviado" }, @@ -1296,6 +1335,9 @@ "userName": { "message": "Nombre de usuario" }, + "validFileImport": { + "message": "Selecciona un archivo válido para importar." + }, "viewAccount": { "message": "Ver cuenta" }, diff --git a/app/_locales/et/messages.json b/app/_locales/et/messages.json index ad64a32cb..c779dff85 100644 --- a/app/_locales/et/messages.json +++ b/app/_locales/et/messages.json @@ -1,21 +1,33 @@ { + "privacyModeDefault": { + "message": "Privaatsusrežiim on nüüd vaikimisi lubatud" + }, "chartOnlyAvailableEth": { "message": "Tabel on saadaval vaid Ethereumi võrkudes." }, + "confirmClear": { + "message": "Kas soovite kindlasti kinnitatud veebisaidid kustutada?" + }, "contractInteraction": { "message": "Lepingu suhtlus" }, + "clearApprovalData": { + "message": "Tühjenda privaatsusandmed" + }, "reject": { "message": "Lükka tagasi" }, - "likeToConnect": { + "providerRequest": { "message": "$1 soovib teie kontoga ühenduse luua" }, + "providerRequestInfo": { + "message": "See sait taotleb juurdepääsu teie praeguse konto aadressi vaatamiseks. Veenduge alati, et usaldate saite, millega suhtlete." + }, "about": { "message": "Teave" }, "aboutSettingsDescription": { - "message": "Versioon, tugikeskus ja kontaktteave" + "message": "Versioon, tugikeskus ja kontaktteave." }, "acceleratingATransaction": { "message": "* Tehingu kiirendamine kõrgemate gaasihindadega suurendab võimalust kiiremaks võrgus töötlemiseks, kuid see ei ole alati tagatud." @@ -51,7 +63,7 @@ "message": "Täpsemad" }, "advancedSettingsDescription": { - "message": "Juurdepääs arendaja funktsioonidele, olekulogide allalaadimine, konto lähtestamine, testvõrkude ja kohandatud RPC-de seadistamine" + "message": "Juurdepääs arendaja funktsioonidele, olekulogide allalaadimine, konto lähtestamine, testvõrkude ja kohandatud RPC-de seadistamine." }, "advancedOptions": { "message": "Täpsemad suvandid" @@ -145,6 +157,10 @@ "basic": { "message": "Põhiseaded" }, + "betweenMinAndMax": { + "message": "peab olema suurem või võrdne $1 ja väiksem või võrdne $2.", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerUrl": { "message": "Blokeeri Explorer" }, @@ -236,6 +252,9 @@ "connect": { "message": "Ühendamine" }, + "connectRequest": { + "message": "Ühenduse taotlus" + }, "connectingTo": { "message": "Ühenduse loomine $1" }, @@ -275,6 +294,9 @@ "copiedExclamation": { "message": "Kopeeritud!" }, + "copy": { + "message": "Kopeeri" + }, "copyAddress": { "message": "Kopeeri aadress lõikelauale" }, @@ -353,6 +375,9 @@ "directDepositEtherExplainer": { "message": "Kui teil on juba veidi eetrit, on kiirem viis eetri rahakotti saamiseks otsene sissemakse." }, + "dismiss": { + "message": "Loobu" + }, "done": { "message": "Valmis" }, @@ -402,7 +427,7 @@ "message": "Kui teil on küsimusi või näete midagi kahtlast, kirjutage meile support@metamask.io." }, "endOfFlowMessage8": { - "message": "MetaMask ei saa teie seemnefraasi taastada. Lisateave." + "message": "Metamask ei saa teie seemnefraasi taastada. Lisateave." }, "endOfFlowMessage9": { "message": "Lisateave." @@ -514,6 +539,10 @@ "getStarted": { "message": "Alustamine" }, + "greaterThanMin": { + "message": "peab olema $1või suurem.", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "Meil on hea meel teid näha." }, @@ -629,6 +658,10 @@ "ledgerAccountRestriction": { "message": "Enne uue konto loomist peate kasutama eelmist kontot." }, + "lessThanMax": { + "message": "peab olema $1või väiksem.", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "Jah, hakkame pihta!" }, @@ -827,6 +860,9 @@ "message": "Kleepige oma privaatne võtmestring siia:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Kleepige oma seemnefraas siia!" + }, "pending": { "message": "ootel" }, @@ -1140,6 +1176,9 @@ "storePhrase": { "message": "Salvestage see fraas paroolihaldurisse, nagu 1Password." }, + "submit": { + "message": "Esita" + }, "submitted": { "message": "Edastatud" }, @@ -1162,7 +1201,7 @@ "message": "Saate sünkroonida oma kontod ja teabe oma mobiiliseadmega. Avage MetaMaski mobiilirakendus, avage \"Settings\" (Seaded) ja puudutage valikut \"Sync from Browser Extension\" (Sünkroonimine lehitseja laiendusest)" }, "syncWithMobileDescNewUsers": { - "message": "Järgige MetaMaski mobiilirakenduse esmakordsel avamisel telefonis esitatud samme." + "message": "Järgige Metamaski mobiilirakenduse esmakordsel avamisel telefonis esitatud samme." }, "syncWithMobileScanThisCode": { "message": "Skanneerige see kood MetaMaski mobiilirakendusega" @@ -1307,6 +1346,9 @@ "userName": { "message": "Kasutajanimi" }, + "validFileImport": { + "message": "Peate valima importimiseks sobiva faili." + }, "viewAccount": { "message": "Kuva konto" }, diff --git a/app/_locales/fa/messages.json b/app/_locales/fa/messages.json index 5be054e7c..5e00dde11 100644 --- a/app/_locales/fa/messages.json +++ b/app/_locales/fa/messages.json @@ -1,16 +1,28 @@ { + "privacyModeDefault": { + "message": "وضعیت محرمیت حالا بصورت خودکار فعال است" + }, "chartOnlyAvailableEth": { "message": "تنها قابل دسترس را در شبکه های ایتریوم جدول بندی نمایید" }, + "confirmClear": { + "message": "آیا مطمئن هستید تا وب سایت های تصدیق شده را حذف کنید؟" + }, "contractInteraction": { "message": "تعامل قرارداد" }, + "clearApprovalData": { + "message": "حذف اطلاعات حریم خصوصی" + }, "reject": { "message": "عدم پذیرش" }, - "likeToConnect": { + "providerRequest": { "message": "1$1 میخواهید تا با حساب تان وصل شوید" }, + "providerRequestInfo": { + "message": "این سایت در حال درخواست دسترسی است تا آدرس فعلی حساب تان را مشاهده نماید. همیشه متوجه باشید که بالای سایتی که با آن معامله میکنید، اعتماد دارید یا خیر." + }, "about": { "message": "درباره" }, @@ -145,6 +157,10 @@ "basic": { "message": "پایه" }, + "betweenMinAndMax": { + "message": "باید بزرگتر از یا مساوی به 1$1 و کوچکتر یا مساوی به 2$2 باشد.", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerUrl": { "message": "بلاک کردن براوزر" }, @@ -236,6 +252,9 @@ "connect": { "message": "اتصال" }, + "connectRequest": { + "message": "درخواست اتصال" + }, "connectingTo": { "message": "در حال اتصال به 1$1" }, @@ -275,6 +294,9 @@ "copiedExclamation": { "message": "کپی شد!" }, + "copy": { + "message": "کپی" + }, "copyAddress": { "message": "کاپی آدرس به کلیپ بورد" }, @@ -353,6 +375,9 @@ "directDepositEtherExplainer": { "message": "در صورتیکه شما کدام ایتر داشته باشید، سریعترین روش برای گرفتن ایتر در کیف جدید تان توسط پرداخت مستقیم." }, + "dismiss": { + "message": "لغو کردن" + }, "done": { "message": "تمام" }, @@ -518,6 +543,10 @@ "getStarted": { "message": "شروع به کار" }, + "greaterThanMin": { + "message": "باید بزرگتر از یا مساوی به 1$1 باشد.", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "از دیدن شما خوشحال هستیم." }, @@ -636,6 +665,10 @@ "ledgerAccountRestriction": { "message": "لازم است تا حساب قبلی تان را قبل از اینکه جدید اضافه کنید، مورد استفاده قرار دهید." }, + "lessThanMax": { + "message": "باید کوچکتر از 1$1 یا مساوی با آن باشد.", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "بلی، درست شد!" }, @@ -837,6 +870,9 @@ "message": "زنجیره کلید شخصی را در اینجا کاپی کنید:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "عبارت بازیاب تان را اینجا بگذارید!" + }, "pending": { "message": "در حال تعلیق" }, @@ -1150,6 +1186,9 @@ "storePhrase": { "message": "این عبارت را در یک نرم افزار مدیریت رمز عبور مانند 1Password ذخیره نمایید." }, + "submit": { + "message": "ارائه" + }, "submitted": { "message": "ارائه شد" }, @@ -1317,6 +1356,9 @@ "userName": { "message": "نام کاربری" }, + "validFileImport": { + "message": "شما باید یک فایل معتبر را جهت وارد سازی انتخاب کنید." + }, "viewAccount": { "message": "مشاهده حساب" }, diff --git a/app/_locales/fi/messages.json b/app/_locales/fi/messages.json index 56f48a6fd..df4f62a19 100644 --- a/app/_locales/fi/messages.json +++ b/app/_locales/fi/messages.json @@ -1,21 +1,33 @@ { + "privacyModeDefault": { + "message": "Yksityisyystila on nyt oletusarvoisesti käytössä" + }, "chartOnlyAvailableEth": { "message": "Kaavio saatavilla vain Ethereum-verkoissa." }, + "confirmClear": { + "message": "Haluatko varmasti tyhjentää hyväksytyt verkkosivustot?" + }, "contractInteraction": { "message": "Sopimustoiminta" }, + "clearApprovalData": { + "message": "Tyhjennä yksityisyystiedot" + }, "reject": { "message": "Hylkää" }, - "likeToConnect": { + "providerRequest": { "message": "$1 haluaisi yhdistää tiliisi" }, + "providerRequestInfo": { + "message": "Tämä sivusto pyytää oikeuksia nähdä nykyisen tiliosoitteesi. Varmista aina, että luotat sivustoihin, joiden kanssa toimit." + }, "about": { "message": "Tietoja asetuksista" }, "aboutSettingsDescription": { - "message": "Versio, tukikeskus ja yhteystiedot" + "message": "Versio, tukikeskus ja yhteystiedot." }, "acceleratingATransaction": { "message": "* Tapahtuman nopeuttaminen käyttämällä korkeampaa gas-hintaa parantaa mahdollisuutta, että verkko käsittelee sen nopeammin, mutta tämä ei ole aina taattua." @@ -51,7 +63,7 @@ "message": "Lisäasetukset" }, "advancedSettingsDescription": { - "message": "Käytä kehittäjän ominaisuuksia, lataa tilalokeja, palauta tilit, asenna testiverkostoja ja muokattavia RPC:itä" + "message": "Käytä kehittäjän ominaisuuksia, lataa tilalokeja, palauta tilit, asenna testiverkostoja ja muokattavia RPC:itä." }, "advancedOptions": { "message": "Tarkemmat vaihtoehdot" @@ -145,6 +157,10 @@ "basic": { "message": "Perusvaihtoehdot" }, + "betweenMinAndMax": { + "message": "tulee olla vähintään $1 ja korkeintaan $2.", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerUrl": { "message": "Lohkonhallinta" }, @@ -233,6 +249,9 @@ "connect": { "message": "Muodosta yhteys" }, + "connectRequest": { + "message": "Yhdistämispyyntö" + }, "connectingTo": { "message": "Yhdistetään summaan $1 " }, @@ -272,6 +291,9 @@ "copiedExclamation": { "message": "Kopioitu" }, + "copy": { + "message": "Kopioi" + }, "copyAddress": { "message": "Kopioi osoite leikepöydälle" }, @@ -350,6 +372,9 @@ "directDepositEtherExplainer": { "message": "Jos sinulla on jo etheriä, nopein tapa hankkia etheriä uuteen lompakkoosi on suoratalletus." }, + "dismiss": { + "message": "Piilota" + }, "done": { "message": "Valmis" }, @@ -515,6 +540,10 @@ "getStarted": { "message": "Aloitusopas" }, + "greaterThanMin": { + "message": "on oltava vähintään $1.", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "Meistä on mukava nähdä sinut." }, @@ -633,6 +662,10 @@ "ledgerAccountRestriction": { "message": "Sinun tarvitsee käyttää edellistä tiliäsi ennen kuin voit lisätä uuden." }, + "lessThanMax": { + "message": "tulee olla korkeintaan $1.", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "Kyllä, valmistaudutaan!" }, @@ -834,6 +867,9 @@ "message": "Liitä yksityisen avaimesi merkkijono tähän:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Liitä salaustekstisi tähän!" + }, "pending": { "message": "odottaa" }, @@ -1147,6 +1183,9 @@ "storePhrase": { "message": "Tallenna tämä teksti johonkin salasanojen hallintaohjelmaan (esim. 1Password)." }, + "submit": { + "message": "Lähetä" + }, "submitted": { "message": "Lähetetty" }, @@ -1314,6 +1353,9 @@ "userName": { "message": "Käyttäjätunnus" }, + "validFileImport": { + "message": "Valittava tuotavaksi kelpaava tiedosto." + }, "viewAccount": { "message": "Näytä tili" }, diff --git a/app/_locales/fil/messages.json b/app/_locales/fil/messages.json index cd9b31234..d00726811 100644 --- a/app/_locales/fil/messages.json +++ b/app/_locales/fil/messages.json @@ -1,21 +1,33 @@ { + "privacyModeDefault": { + "message": "Naka-enable ang Privacy Mode bilang default" + }, "chartOnlyAvailableEth": { "message": "Available lang ang chart sa mga Ethereum network." }, + "confirmClear": { + "message": "Sigurado ka bang gusto mong i-clear ang mga inaprubahang website?" + }, "contractInteraction": { "message": "Paggamit sa Contract" }, + "clearApprovalData": { + "message": "I-clear ang Privacy Data" + }, "reject": { "message": "Tanggihan" }, - "likeToConnect": { + "providerRequest": { "message": "Gusto ng $1 na kumonekta sa iyong account" }, + "providerRequestInfo": { + "message": "Humihiling ng access ang site na ito na tingnan ang kasalukuyan mong account address. Palaging tiyaking pinagkakatiwalaan mo ang mga site na pinupuntahan mo." + }, "about": { "message": "Tungkol sa" }, "aboutSettingsDescription": { - "message": "Bersyon, support center, at impormasyon sa pakikipag-ugnayan" + "message": "Bersyon, support center, at impormasyon sa pakikipag-ugnayan." }, "acceleratingATransaction": { "message": "* Ang pagpapabilis sa isang transaksyon sa pamamagitan ng paggamit ng mas mataas na presyo ng gas ay makakadagdag sa tsansa nitong maproseso ng network nang mas mabilis, pero hindi ito palaging garantisado." @@ -45,7 +57,7 @@ "message": "Magdagdag ng Recipient" }, "advancedSettingsDescription": { - "message": "I-access ang mga feature para sa mga developer, mag-download ng mga State Log, I-reset ang Account, mag-set up ng mga testnet at custom RPC" + "message": "I-access ang mga feature para sa mga developer, mag-download ng mga State Log, I-reset ang Account, mag-set up ng mga testnet at custom RPC." }, "advancedOptions": { "message": "Mga Advanced na Opsyon" @@ -133,6 +145,10 @@ "basic": { "message": "Pangunahin" }, + "betweenMinAndMax": { + "message": "dapat ay mas malaki sa o katumbas ng $1 at mas maliit sa o katumbas ng $2.", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerView": { "message": "Tingnan ang account sa $1", "description": "$1 replaced by URL for custom block explorer" @@ -326,6 +342,9 @@ "directDepositEtherExplainer": { "message": "Kung mayroon ka nang Ether, ang pinakamabilis na paraan para magkaroon ng Ether sa iyong bagong wallet ay sa pamamagitan ng direkang deposito." }, + "dismiss": { + "message": "Balewalain" + }, "done": { "message": "Tapos na" }, @@ -484,6 +503,10 @@ "getStarted": { "message": "Magsimula" }, + "greaterThanMin": { + "message": "dapat ay mas malaki sa o katumbas ng $1.", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "Masaya kaming makita ka." }, @@ -583,6 +606,10 @@ "ledgerAccountRestriction": { "message": "Kailangan mong gamitin ang iyong dating account bago ka makapagdagdag ng bago." }, + "lessThanMax": { + "message": "dapat ay mas mababa sa o katumbas ng $1.", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "Oo, i-set up natin ito!" }, @@ -765,6 +792,9 @@ "message": "I-paste ang iyong private key string dito:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "I-paste ang iyong seed phrase dito!" + }, "pending": { "message": "nakabinbin" }, @@ -1053,6 +1083,9 @@ "storePhrase": { "message": "I-store ang pariralang ito sa isang password manager tulad ng 1Password." }, + "submit": { + "message": "Isumite" + }, "submitted": { "message": "Isinumite" }, @@ -1208,6 +1241,9 @@ "usedByClients": { "message": "Ginagamit ng iba't ibang client" }, + "validFileImport": { + "message": "Dapat kang pumili ng valid na file na ii-import." + }, "viewAccount": { "message": "Tingnan ang Account" }, diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json index 1fc2a600d..98ae1576b 100644 --- a/app/_locales/fr/messages.json +++ b/app/_locales/fr/messages.json @@ -1,21 +1,33 @@ { + "privacyModeDefault": { + "message": "Le mode conversation privée est maintenant activé par défaut" + }, "chartOnlyAvailableEth": { "message": "Tableau disponible uniquement sur les réseaux Ethereum." }, + "confirmClear": { + "message": "Êtes-vous sûr de vouloir supprimer les sites Web approuvés?" + }, "contractInteraction": { "message": "Interaction avec un contrat" }, + "clearApprovalData": { + "message": "Effacer les données d'approbation" + }, "reject": { "message": "Rejeter" }, - "likeToConnect": { + "providerRequest": { "message": "$1 voudrait se connecter à votre compte" }, + "providerRequestInfo": { + "message": "Le domaine répertorié ci-dessous tente de demander l'accès à l'API Ethereum pour pouvoir interagir avec la chaîne de blocs Ethereum. Vérifiez toujours que vous êtes sur le bon site avant d'autoriser l'accès à Ethereum." + }, "about": { "message": "À propos" }, "aboutSettingsDescription": { - "message": "Version, centre d'assistance et coordonnées" + "message": "Version, centre d'assistance et coordonnées." }, "acceleratingATransaction": { "message": "* Accélérer une transaction en utilisant un prix de l'essence plus élevé augmente ses chances d'être traitée plus rapidement par le réseau, mais ce n'est pas toujours garanti." @@ -51,7 +63,7 @@ "message": "Paramètres avancés" }, "advancedSettingsDescription": { - "message": "Accédez aux fonctionnalités pour les développeurs, téléchargez State Logs, réinitialisez votre compte, configurez testnets et personnalisez RPC" + "message": "Accédez aux fonctionnalités pour les développeurs, téléchargez State Logs, réinitialisez votre compte, configurez testnets et personnalisez RPC." }, "advancedOptions": { "message": "Options avancées" @@ -142,6 +154,10 @@ "basic": { "message": "Général" }, + "betweenMinAndMax": { + "message": "doit être supérieur ou égal à $1 et inférieur ou égal à $2", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerView": { "message": "Afficher le compte à $1", "description": "$1 replaced by URL for custom block explorer" @@ -224,6 +240,9 @@ "connect": { "message": "Connecter" }, + "connectRequest": { + "message": "Demande de connexion" + }, "connectingTo": { "message": "Connexion à $1" }, @@ -263,6 +282,9 @@ "copiedExclamation": { "message": "Copié!" }, + "copy": { + "message": "Copier" + }, "copyAddress": { "message": "Copier l'addresse dans le presse-papier" }, @@ -341,6 +363,9 @@ "directDepositEtherExplainer": { "message": "Si vous avez déjà de l'Ether, le moyen le plus rapide d'obtenir des Ether dans votre nouveau portefeuille est par dépôt direct." }, + "dismiss": { + "message": "Ignorer" + }, "done": { "message": "Terminé" }, @@ -506,6 +531,10 @@ "getStarted": { "message": "Démarrer" }, + "greaterThanMin": { + "message": "doit être supérieur ou égal à $1.", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "Nous sommes ravis de vous voir." }, @@ -612,6 +641,10 @@ "ledgerAccountRestriction": { "message": "Vous devez d'abord utiliser le dernier compte que vous avez créé avant de pouvoir en créer un autre." }, + "lessThanMax": { + "message": "doit être inférieur ou égal à $1.", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "Oui, passons à la configuration !" }, @@ -804,6 +837,9 @@ "message": "Collez votre clé privée ici:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Collez votre seed phrase ici!" + }, "pending": { "message": "En attente" }, @@ -1117,6 +1153,9 @@ "storePhrase": { "message": "Stockez cette phrase dans un gestionnaire de mots de passe comme 1Password." }, + "submit": { + "message": "Soumettre" + }, "submitted": { "message": "Envoyé" }, @@ -1278,6 +1317,9 @@ "userName": { "message": "Nom d'utilisateur" }, + "validFileImport": { + "message": "Vous devez selectionner un fichier valide à importer." + }, "viewAccount": { "message": "Afficher le compte" }, diff --git a/app/_locales/gu/messages.json b/app/_locales/gu/messages.json index d04514fd9..bcd467be1 100644 --- a/app/_locales/gu/messages.json +++ b/app/_locales/gu/messages.json @@ -42,6 +42,9 @@ "connect": { "message": "કનેક્ટ કરો" }, + "copy": { + "message": "કૉપિ કરો" + }, "copyToClipboard": { "message": "ક્લિપબોર્ડ પર કૉપિ કરો" }, @@ -54,6 +57,9 @@ "details": { "message": "વિગતો" }, + "dismiss": { + "message": "કાઢી નાખો" + }, "done": { "message": "થઈ ગયું" }, @@ -125,6 +131,9 @@ "settings": { "message": "સેટિંગ્સ" }, + "submit": { + "message": "સબમિટ કરો" + }, "tryAgain": { "message": "ફરી પ્રયાસ કરો" }, diff --git a/app/_locales/he/messages.json b/app/_locales/he/messages.json index d0f92848c..41813baf2 100644 --- a/app/_locales/he/messages.json +++ b/app/_locales/he/messages.json @@ -1,16 +1,28 @@ { + "privacyModeDefault": { + "message": "מצב פרטיות זמין עכשיו כברירת מחדל" + }, "chartOnlyAvailableEth": { "message": "טבלה זמינה רק ברשתות אתריום." }, + "confirmClear": { + "message": "הנך בטוח/ה כי ברצונך למחוק אתרים שאושרו?" + }, "contractInteraction": { "message": "אינטראקציית חוזה" }, + "clearApprovalData": { + "message": "נקה נתוני פרטיות" + }, "reject": { "message": "דחה" }, - "likeToConnect": { + "providerRequest": { "message": "$1 מבקש להתחבר לחשבון שלך" }, + "providerRequestInfo": { + "message": "אתר זה מבקש גישה לצפייה בכתובת החשבון הנוכחית שלך. יש לוודא תמיד כי הנך בוטח/ת באתרים עמם הנך מתקשר/ת." + }, "about": { "message": "מידע כללי" }, @@ -145,6 +157,10 @@ "basic": { "message": "בסיסי" }, + "betweenMinAndMax": { + "message": "חייב להיות גדול או שווה ל-$1 ופחות או שווה ל-$2.", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerUrl": { "message": "סייר בלוקים" }, @@ -236,6 +252,9 @@ "connect": { "message": "התחברות" }, + "connectRequest": { + "message": "חבר/י בקשה" + }, "connectingTo": { "message": "מתחבר ל- $1 " }, @@ -275,6 +294,9 @@ "copiedExclamation": { "message": "הועתק!" }, + "copy": { + "message": "העתק" + }, "copyAddress": { "message": "העתק כתובת ללוח" }, @@ -353,6 +375,9 @@ "directDepositEtherExplainer": { "message": "אם כבר יש ברשותך את'ר (Ether) , הדרך המהירה ביותר להכניס את'ר לארנק החדש שלך היא באמצעות הפקדה ישירה." }, + "dismiss": { + "message": "סגור" + }, "done": { "message": "סיום" }, @@ -518,6 +543,10 @@ "getStarted": { "message": "תחילת העבודה" }, + "greaterThanMin": { + "message": "חייב להיות גדול או שווה ל-$1.", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "אנו שמחים לראותך." }, @@ -636,6 +665,10 @@ "ledgerAccountRestriction": { "message": "עליך להשתמש בחשבון האחרון שלך לפני שתוכל/י להוסיף חשבון חדש." }, + "lessThanMax": { + "message": "חייב להיות פחות או שווה ל- $1.", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "כן, בוא נתקין!" }, @@ -834,6 +867,9 @@ "message": "הדבק/י את מחרוזת המפתח הפרטי שלך כאן:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "הדבק/י את ה-seed phrase של כאן!" + }, "pending": { "message": "בהמתנה" }, @@ -1144,6 +1180,9 @@ "storePhrase": { "message": "אחסנ/י צירוף זה במנהל ססמאות כמו 1Password." }, + "submit": { + "message": "שלח" + }, "submitted": { "message": "הוגש" }, @@ -1311,6 +1350,9 @@ "userName": { "message": "שם משתמש" }, + "validFileImport": { + "message": "עליך לבחור קובץ חוקי לייבוא." + }, "viewAccount": { "message": "הצג חשבון" }, diff --git a/app/_locales/hi/messages.json b/app/_locales/hi/messages.json index 51aa21b84..19834411e 100644 --- a/app/_locales/hi/messages.json +++ b/app/_locales/hi/messages.json @@ -1,16 +1,28 @@ { + "privacyModeDefault": { + "message": "गोपनीय मोड अब डिफ़ॉल्ट रूप से सक्षम है" + }, "chartOnlyAvailableEth": { "message": "केवल ईथरअम नेटवर्क पर उपलब्ध चार्ट।" }, + "confirmClear": { + "message": "क्या आप वाकई स्वीकृत वेबसाइटों को क्लियर करना चाहते हैं?" + }, "contractInteraction": { "message": "कॉन्ट्रैक्ट की बातचीत" }, + "clearApprovalData": { + "message": "गोपनीयता डेटा रिक्त करें" + }, "reject": { "message": "अस्‍वीकार करें" }, - "likeToConnect": { + "providerRequest": { "message": "$1 आपके खाते से कनेक्ट होता चाहता हैं" }, + "providerRequestInfo": { + "message": "यह साइट आपके वर्तमान खाते का पता देखने के लिए एक्सेस का अनुरोध कर रही है। हमेशा सुनिश्चित करें कि आप जिन साइटों पर जाते हैं वे विश्वसनीय हैं।" + }, "about": { "message": "इसके बारे में" }, @@ -145,6 +157,10 @@ "basic": { "message": "मूलभूत" }, + "betweenMinAndMax": { + "message": "$1 से अधिक या बराबर होना चाहिए और $2 के बराबर या उससे कम होना चाहिए।", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerUrl": { "message": "एक्सप्लोरर को ब्लॉक करें" }, @@ -236,6 +252,9 @@ "connect": { "message": "कनेक्ट करें" }, + "connectRequest": { + "message": "संपर्क अनुरोध" + }, "connectingTo": { "message": "$1 से कनेक्ट हो रहा है" }, @@ -275,6 +294,9 @@ "copiedExclamation": { "message": "कॉपी की गई!" }, + "copy": { + "message": "कॉपी बनाएं" + }, "copyAddress": { "message": "क्लिपबोर्ड पर पता कॉपी करें" }, @@ -353,6 +375,9 @@ "directDepositEtherExplainer": { "message": "यदि आपके पास पहले से ही कुछ Ether हैं, तो अपने नए वॉलेट में Ether पाने का सबसे तेज़ तरीका सीधे जमा करना है।" }, + "dismiss": { + "message": "खारिज करें" + }, "done": { "message": "पूर्ण" }, @@ -518,6 +543,10 @@ "getStarted": { "message": "आरंभ करें" }, + "greaterThanMin": { + "message": "$1 से बड़ा या उसके बराबर होना चाहिए।", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "आपको देखकर हमें बहुत ख़ुशी हुई।" }, @@ -636,6 +665,10 @@ "ledgerAccountRestriction": { "message": "नया खाता जोड़ने से पहले आपको अपने पिछले खाते का उपयोग करना होगा।" }, + "lessThanMax": { + "message": "$1 से कम या उसके बराबर होना चाहिए।", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "जी हां, सेट होने दें!" }, @@ -834,6 +867,9 @@ "message": "अपनी निजी कुंजी स्ट्रिंग यहां पेस्ट करें:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "अपना बीज वाक्यांश यहाँ पेस्ट करें!" + }, "pending": { "message": "विलंबित" }, @@ -1144,6 +1180,9 @@ "storePhrase": { "message": "इस वाक्यांश को 1पासवर्ड जैसे पासवर्ड मैनेजर में संग्रहीत करें।" }, + "submit": { + "message": "सबमिट करें" + }, "submitted": { "message": "प्रस्तुत किया गया" }, @@ -1311,6 +1350,9 @@ "userName": { "message": "उपयोगकर्ता नाम" }, + "validFileImport": { + "message": "आयात करने के लिए आपको एक मान्य फ़ाइल को चुनना होगा।" + }, "viewAccount": { "message": "खाता देखें" }, diff --git a/app/_locales/hn/messages.json b/app/_locales/hn/messages.json index 800907798..91d9bde7e 100644 --- a/app/_locales/hn/messages.json +++ b/app/_locales/hn/messages.json @@ -1,10 +1,19 @@ { + "confirmClear": { + "message": "क्या आप वाकई अनुमोदित वेबसाइटों को साफ़ करना चाहते हैं?" + }, + "clearApprovalData": { + "message": "अनुमोदन डेटा साफ़ करें" + }, "approve": { "message": "मंजूर" }, "reject": { "message": "अस्वीकार" }, + "providerRequestInfo": { + "message": "नीचे सूचीबद्ध डोमेन वेब 3 एपीआई तक पहुंच का अनुरोध करने का प्रयास कर रहा है ताकि यह एथेरियम ब्लॉकचेन से बातचीत कर सके। वेब 3 एक्सेस को मंजूरी देने से पहले हमेशा सही जांच करें कि आप सही साइट पर हैं।" + }, "account": { "message": "खाता" }, @@ -46,6 +55,10 @@ "balanceIsInsufficientGas": { "message": "वर्तमान गैस कुल के लिए अपर्याप्त शेष" }, + "betweenMinAndMax": { + "message": "$1 के बराबर या ज्यदा या, $2 के बराबर या कम होना चाहिए।", + "description": "हेक्स इनपुट के लिए दशमलव इनपुट के रूप में सहायक" + }, "blockiesIdenticon": { "message": "ब्लॉकीज पहचान का उपयोग करें" }, @@ -82,6 +95,9 @@ "copiedExclamation": { "message": "कॉपी कर दिया गया!" }, + "copy": { + "message": "कॉपी / प्रतिलिपि कर्रे" + }, "copyToClipboard": { "message": "क्लिपबोर्ड पर कॉपी करें" }, @@ -187,6 +203,10 @@ "message": "$1 के लिए एक नल से ईथर प्राप्त करें", "description": "ईथर नल के लिए नेटवर्क नाम प्रदर्शित करता है" }, + "greaterThanMin": { + "message": "$1 के बराबर या बराबर होना चाहिए।", + "description": "हेक्स इनपुट के लिए दशमलव इनपुट के रूप में सहायक" + }, "here": { "message": "यहां", "description": "अधिक जानकारी के लिए यहां क्लिक करें- (परेशानी के साथ जाता है टोकनबैलेंस) (troubleTokenBalances)" @@ -245,6 +265,10 @@ "kovan": { "message": "कोवान टेस्ट नेटवर्क" }, + "lessThanMax": { + "message": "$1 से कम या बराबर होना चाहिए।", + "description": "हेक्स इनपुट के लिए दशमलव इनपुट के रूप में सहायक" + }, "likeToAddTokens": { "message": "क्या आप इन टोकनों को जोड़ना चाहते हैं?" }, @@ -320,6 +344,9 @@ "message": "यहां अपनी निजी कुंजी स्ट्रिंग चिपकाएं:", "description": "किसी निजी कुंजी से किसी खाते को आयात करने के लिए" }, + "pasteSeed": { + "message": "यहां अपने बीज वाक्यांश पेस्ट करें!" + }, "personalAddressDetected": { "message": "व्यक्तिगत पता मिला। टोकन अनुबंध का पता इनपुट।" }, @@ -420,6 +447,9 @@ "stateLogsDescription": { "message": "स्थिति संदेश में आपका सार्वजनिक खाता, पतों और भेजे गए लेनदेन, होते हैं।" }, + "submit": { + "message": "सबमिट करें" + }, "supportCenter": { "message": "हमारे सहायता केंद्र पर जाएं" }, @@ -467,6 +497,9 @@ "usedByClients": { "message": "विभिन्न क्लाइंट्स द्वारा उपयोग किया जाता है" }, + "validFileImport": { + "message": "आयात करने के लिए आपको एक वैध फ़ाइल चुननी होगी।" + }, "viewAccount": { "message": "खाता देखें" }, diff --git a/app/_locales/hr/messages.json b/app/_locales/hr/messages.json index 57b4b0d4c..ba64748c2 100644 --- a/app/_locales/hr/messages.json +++ b/app/_locales/hr/messages.json @@ -1,10 +1,19 @@ { + "privacyModeDefault": { + "message": "Način se Privatnost sada zadano omogućava" + }, "chartOnlyAvailableEth": { "message": "Grafikon je dostupan samo na mrežama Ethereum." }, + "confirmClear": { + "message": "Sigurno želite očistiti odobrena mrežna mjesta?" + }, "contractInteraction": { "message": "Ugovorna interakcija" }, + "clearApprovalData": { + "message": "Očisti podatke o privatnosti" + }, "appName": { "message": "MetaMask", "description": "The name of the application" @@ -12,14 +21,17 @@ "reject": { "message": "Odbaci" }, - "likeToConnect": { + "providerRequest": { "message": "Korisnik $1 želi se povezati na vaš račun" }, + "providerRequestInfo": { + "message": "Na ovom se mjestu zahtijeva pristup za pregledavanje vaše trenutačne adrese računa. Uvijek pazite da vjerujete mrežnim mjestima s kojima rukujete." + }, "about": { "message": "O opcijama" }, "aboutSettingsDescription": { - "message": "Inačica, centar za podršku i informacije za kontakt" + "message": "Inačica, centar za podršku i informacije za kontakt." }, "acceleratingATransaction": { "message": "* Ubrzavanjem se transakcije pomoću veće cijene goriva povećava šansa za bržu obradu mrežom, ali se uvijek ne jamči." @@ -55,7 +67,7 @@ "message": "Napredno" }, "advancedSettingsDescription": { - "message": "Pristup značajkama razvojnog inženjera, preuzimanje zapisnika stanja, poništavanje računa, postavljanje testnih mreža i prilagođeni RPC" + "message": "Pristup značajkama razvojnog inženjera, preuzimanje zapisnika stanja, poništavanje računa, postavljanje testnih mreža i prilagođeni RPC." }, "advancedOptions": { "message": "Napredne mogućnosti" @@ -145,6 +157,10 @@ "basic": { "message": "Osnovne" }, + "betweenMinAndMax": { + "message": "mora biti veće od $1 ili jednako te manje od $2 ili jednako.", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerUrl": { "message": "Blokiraj Explorer" }, @@ -236,6 +252,9 @@ "connect": { "message": "Povežite se" }, + "connectRequest": { + "message": "Zahtjev za povezivanjem" + }, "connectingTo": { "message": "Povezivanje na $1" }, @@ -275,6 +294,9 @@ "copiedExclamation": { "message": "Kopirano!" }, + "copy": { + "message": "Kopiraj" + }, "copyAddress": { "message": "Kopiraj adresu u međuspremnik" }, @@ -353,6 +375,9 @@ "directDepositEtherExplainer": { "message": "Ako imate nešto Ethera, najbrži je način prebacivanja Ethera u vaš novi novčanik izravan polog." }, + "dismiss": { + "message": "Odbaci" + }, "done": { "message": "Gotovo" }, @@ -514,6 +539,10 @@ "getStarted": { "message": "Početak upotrebe" }, + "greaterThanMin": { + "message": "mora biti veće od $1 ili jednako.", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "Sretni smo što ste tu." }, @@ -632,6 +661,10 @@ "ledgerAccountRestriction": { "message": "Treba se koristiti zadnjim računom kako biste dodali novi račun." }, + "lessThanMax": { + "message": "mora biti manje od $1 ili jednako.", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "Da, obavimo postavljanje!" }, @@ -830,6 +863,9 @@ "message": "Ovdje zalijepite svoj privatni niz ključa:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Ovdje zalijepite svoju početnu rečenicu!" + }, "pending": { "message": "na čekanju" }, @@ -1143,6 +1179,9 @@ "storePhrase": { "message": "Spremite ovu rečenicu u upravitelj lozinkama poput aplikacije 1Password." }, + "submit": { + "message": "Pošalji" + }, "submitted": { "message": "Poslano" }, @@ -1307,6 +1346,9 @@ "userName": { "message": "Korisničko ime" }, + "validFileImport": { + "message": "Morate odabrati valjanu datoteku za uvoz." + }, "viewAccount": { "message": "Prikaz računa" }, diff --git a/app/_locales/ht/messages.json b/app/_locales/ht/messages.json index dd63dce0c..d973aeef5 100644 --- a/app/_locales/ht/messages.json +++ b/app/_locales/ht/messages.json @@ -1,4 +1,13 @@ { + "confirmClear": { + "message": "Èske ou sèten ou vle klè sitwèb apwouve?" + }, + "clearApprovalData": { + "message": "Klè Done sou vi prive" + }, + "providerRequestInfo": { + "message": "Domèn ki nan lis anba a ap mande pou jwenn aksè a blòkchou Ethereum ak pou wè kont ou ye kounye a. Toujou double tcheke ke ou sou sit ki kòrèk la anvan apwouve aksè." + }, "accessingYourCamera": { "message": "Aksè a Kamera" }, @@ -70,6 +79,10 @@ "balanceIsInsufficientGas": { "message": "Ensifizan balans pou total gaz aktyèl la" }, + "betweenMinAndMax": { + "message": "dwe plis pase oswa egal a $ 1 mwens ke oswa egal a $ 2.", + "description": "helper for inputting hex as decimal input" + }, "blockiesIdenticon": { "message": "Itilize Blockies Identicon" }, @@ -148,6 +161,9 @@ "copiedExclamation": { "message": "Kopye!" }, + "copy": { + "message": "Kopye" + }, "copyAddress": { "message": "Kopi adrès clipboard" }, @@ -289,6 +305,10 @@ "getHelp": { "message": "Jwenn èd." }, + "greaterThanMin": { + "message": "dwe pi gran pase oswa egal a $ 1.", + "description": "helper for inputting hex as decimal input" + }, "hardware": { "message": "materyèl" }, @@ -383,6 +403,10 @@ "ledgerAccountRestriction": { "message": "Ou bezwen sèvi ak dènye kont ou anvan ou ka ajoute yon nouvo." }, + "lessThanMax": { + "message": "dwe mwens pase oswa egal a $ 1.", + "description": "helper for inputting hex as decimal input" + }, "likeToAddTokens": { "message": "Èske ou ta renmen ajoute sa nan tokens?" }, @@ -500,6 +524,9 @@ "message": "Kole fraz prive ou a la:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Kole seed fraz ou a la!" + }, "pending": { "message": "l ap mache" }, @@ -729,6 +756,9 @@ "step3HardwareWalletMsg": { "message": "Sèvi ak kont materyèl ou menm jan ou t ap fè pou kont Etherum. Ouvri sesyon an nan dApps, voye Eth, achte ak stòke ERC20 tokens ak e ki pake chanje tokens tankou CryptoKitties." }, + "submit": { + "message": "Soumèt" + }, "submitted": { "message": "Te Soumèt" }, @@ -827,6 +857,9 @@ "usedByClients": { "message": "Itilize pa yon varyete de kliyan diferan" }, + "validFileImport": { + "message": "Ou dwe chwazi yon dosye ki valab pou enpòte." + }, "viewAccount": { "message": "Wè Kont" }, diff --git a/app/_locales/hu/messages.json b/app/_locales/hu/messages.json index 33b8dac32..542534f32 100644 --- a/app/_locales/hu/messages.json +++ b/app/_locales/hu/messages.json @@ -1,10 +1,19 @@ { + "privacyModeDefault": { + "message": "Az adatvédelmi mód mostantól alapbeállításként engedélyezve van" + }, "chartOnlyAvailableEth": { "message": "A diagram csak Ethereum hálózatokon érhető el" }, + "confirmClear": { + "message": "Biztosan törölni szeretnéd a jóváhagyott weboldalakat?" + }, "contractInteraction": { "message": "Szerződéses interakció" }, + "clearApprovalData": { + "message": "Adatvédelmi adatok törlése" + }, "appName": { "message": "MetaMask", "description": "The name of the application" @@ -12,14 +21,17 @@ "reject": { "message": "Elutasítás" }, - "likeToConnect": { + "providerRequest": { "message": "$1 szeretne kapcsolódni az ön fiókjához" }, + "providerRequestInfo": { + "message": "A webhely hozzáférést kér az ön jelenlegi fiókcímének megtekintéséhez. Mindig győződjön meg arról, hogy megbízható webhellyel létesít kapcsolatot." + }, "about": { "message": "Névjegy" }, "aboutSettingsDescription": { - "message": "Verzió, ügyfélszolgálat és elérhetőségek" + "message": "Verzió, ügyfélszolgálat és elérhetőségek." }, "acceleratingATransaction": { "message": "* Ha szeretné felgyorsítani a tranzakciót azzal, hogy magasabb gázárat használ, az növeli a gyorsabb feldolgozás esélyét, de ez nem mindig garantált." @@ -55,7 +67,7 @@ "message": "Speciális" }, "advancedSettingsDescription": { - "message": "Hozzáférés fejlesztői funkciókhoz, állapotnaplók letöltése, fiók újraállítása, testnetek és egyéni RPC-k beállítása" + "message": "Hozzáférés fejlesztői funkciókhoz, állapotnaplók letöltése, fiók újraállítása, testnetek és egyéni RPC-k beállítása." }, "advancedOptions": { "message": "Haladó beállítások" @@ -145,6 +157,10 @@ "basic": { "message": "Alapvető" }, + "betweenMinAndMax": { + "message": "legyen nagyobb vagy egyenlő mint $1 és kisebb vagy egyenlő mint $2.", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerUrl": { "message": "Explorer blokkolása" }, @@ -236,6 +252,9 @@ "connect": { "message": "Csatlakozás" }, + "connectRequest": { + "message": "Csatlakozási kérelem" + }, "connectingTo": { "message": "Kapcsolódás: $1" }, @@ -275,6 +294,9 @@ "copiedExclamation": { "message": "Kimásolva!" }, + "copy": { + "message": "Másolás" + }, "copyAddress": { "message": "Másolja a címet a vágólapra" }, @@ -353,6 +375,9 @@ "directDepositEtherExplainer": { "message": "Amennyiben már rendelkezik némi Ether-rel, a közvetlen letéttel gyorsan elhelyezheti azt új pénztárcájában." }, + "dismiss": { + "message": "Elvetés" + }, "done": { "message": "Kész" }, @@ -514,6 +539,10 @@ "getStarted": { "message": "Első lépések" }, + "greaterThanMin": { + "message": "legyen nagyobb vagy egyenlő mint $1.", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "Örülünk, hogy itt van. " }, @@ -632,6 +661,10 @@ "ledgerAccountRestriction": { "message": "Használnia kell a korábbi fiókját, mielőtt újat adhat hozzá. " }, + "lessThanMax": { + "message": "egyenlő vagy kevesebb kell legyen mint $1.", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "Igen, hozzuk létre!" }, @@ -830,6 +863,9 @@ "message": "Illessze be ide a privát kulcs karakterláncát:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Másolja be ide a seed mondatot!" + }, "pending": { "message": "folyamatban" }, @@ -1143,6 +1179,9 @@ "storePhrase": { "message": "Tárolja a mondatot jelszókezelőben, például az 1Passwordben." }, + "submit": { + "message": "Elküldés" + }, "submitted": { "message": "Elküldve" }, @@ -1307,6 +1346,9 @@ "userName": { "message": "Felhasználónév" }, + "validFileImport": { + "message": "Ki kell választanod egy érvényes fájlt az importáláshoz." + }, "viewAccount": { "message": "Fiók megtekintése" }, diff --git a/app/_locales/id/messages.json b/app/_locales/id/messages.json index 874ca4576..252b7ec9f 100644 --- a/app/_locales/id/messages.json +++ b/app/_locales/id/messages.json @@ -1,10 +1,19 @@ { + "privacyModeDefault": { + "message": "Modus Privasi kini aktif secara default" + }, "chartOnlyAvailableEth": { "message": "Grafik hanya tersedia pada jaringan Ethereum." }, + "confirmClear": { + "message": "Yakin ingin mengosongkan website yang disetujui?" + }, "contractInteraction": { "message": "Interaksi Kontrak" }, + "clearApprovalData": { + "message": "Bersihkan Data Privasi" + }, "appName": { "message": "MetaMask", "description": "The name of the application" @@ -12,14 +21,17 @@ "reject": { "message": "Tolak" }, - "likeToConnect": { + "providerRequest": { "message": "$1 ingin menghubungkan ke akun Anda" }, + "providerRequestInfo": { + "message": "Situs ini meminta akses untuk melihat alamat akun Anda saat ini. Selalu pastikan bahwa Anda bisa mempercayai situs yang berinteraksi dengan Anda." + }, "about": { "message": "Tentang" }, "aboutSettingsDescription": { - "message": "Versi, pusat dukungan, dan informasi kontak" + "message": "Versi, pusat dukungan, dan informasi kontak." }, "acceleratingATransaction": { "message": "* Mempercepat transaksi dengan menggunakan harga gas yang lebih tinggi meningkatkan peluangnya untuk lebih cepat diproses oleh jaringan, tetapi tak selalu terjamin pasti cepat." @@ -55,7 +67,7 @@ "message": "Lanjutan" }, "advancedSettingsDescription": { - "message": "Akses fitur pengembang, unduh Log Status, Atur Ulang Akun, tata testnets dan RPC kustom" + "message": "Akses fitur pengembang, unduh Log Status, Atur Ulang Akun, tata testnets dan RPC kustom." }, "advancedOptions": { "message": "Opsi Lanjutan" @@ -145,6 +157,10 @@ "basic": { "message": "Dasar" }, + "betweenMinAndMax": { + "message": "harus lebih dari atau sama dengan $1 dan kurang dari atau sama dengan $2.", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerUrl": { "message": "Blokir Penjelajah" }, @@ -236,6 +252,9 @@ "connect": { "message": "Sambungkan" }, + "connectRequest": { + "message": "Permintaan Sambungan" + }, "connectingTo": { "message": "Menghubungkan ke $1" }, @@ -275,6 +294,9 @@ "copiedExclamation": { "message": "Disalin!" }, + "copy": { + "message": "Salin" + }, "copyAddress": { "message": "Salin alamat ke clipboard" }, @@ -347,6 +369,9 @@ "directDepositEtherExplainer": { "message": "Jika Anda sudah memiliki Ether, cara tercepat mendapatkan Ether di dompet baru lewat deposit langsung." }, + "dismiss": { + "message": "Tutup" + }, "done": { "message": "Selesai" }, @@ -505,6 +530,10 @@ "getStarted": { "message": "Mulai" }, + "greaterThanMin": { + "message": "harus lebih besar atau sama dengan $1.", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "Kami senang bertemu dengan Anda." }, @@ -623,6 +652,10 @@ "ledgerAccountRestriction": { "message": "Anda perlu menggunakan akun terakhir sebelum dapat menambahkan akun baru." }, + "lessThanMax": { + "message": "harus kurang dari atau setara $1.", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "Ya, ayo tata!" }, @@ -815,6 +848,9 @@ "message": "Tempelkan string kunci privat di sini:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Tempel frasa benih Anda di sini!" + }, "pending": { "message": "tertunda" }, @@ -1128,6 +1164,9 @@ "storePhrase": { "message": "Simpan frasa ini dalam manajer sandi seperti 1Password." }, + "submit": { + "message": "Kirim" + }, "submitted": { "message": "Terkirim" }, @@ -1286,6 +1325,9 @@ "userName": { "message": "Nama Pengguna" }, + "validFileImport": { + "message": "Anda harus memilih berkas yang sah untuk diimpor." + }, "viewAccount": { "message": "Lihat Akun" }, diff --git a/app/_locales/it/messages.json b/app/_locales/it/messages.json index 213407a3b..1d0725183 100644 --- a/app/_locales/it/messages.json +++ b/app/_locales/it/messages.json @@ -1,21 +1,33 @@ { + "privacyModeDefault": { + "message": "La modalità privacy è ora abilitata per impostazione predefinita" + }, "chartOnlyAvailableEth": { "message": "Grafico disponibile solo per le reti Ethereum." }, + "confirmClear": { + "message": "Sei sicuro di voler cancellare i siti Web approvati?" + }, "contractInteraction": { "message": "Interazione Contratto" }, + "clearApprovalData": { + "message": "Cancella i dati di approvazione" + }, "reject": { "message": "Annulla" }, - "likeToConnect": { + "providerRequest": { "message": "$1 vorrebbe connettersi al tuo account" }, + "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" + "message": "Version, centro di supporto e contatti." }, "acceleratingATransaction": { "message": "* Accelerare una transazione usando un prezzo del gas maggiore aumenta la probabilità che la rete la elabori più velocemente, ma non è garantito." @@ -48,7 +60,7 @@ "message": "Avanzate" }, "advancedSettingsDescription": { - "message": "Accedi alle funzionalità sviluppatore, download dei log di Stato, Reset Account, imposta reti di test e RPC personalizzata" + "message": "Accedi alle funzionalità sviluppatore, download dei log di Stato, Reset Account, imposta reti di test e RPC personalizzata." }, "advancedOptions": { "message": "Opzioni Avanzate" @@ -139,6 +151,10 @@ "basic": { "message": "Base" }, + "betweenMinAndMax": { + "message": "deve essere maggiore o uguale a $1 e minore o uguale a $2.", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerView": { "message": "Visualizza account su $1", "description": "$1 replaced by URL for custom block explorer" @@ -215,6 +231,9 @@ "connect": { "message": "Connetti" }, + "connectRequest": { + "message": "Richiesta Connessione" + }, "connectingTo": { "message": "Connessione in corso a $1" }, @@ -254,6 +273,9 @@ "copiedExclamation": { "message": "Copiato!" }, + "copy": { + "message": "Copia" + }, "copyAddress": { "message": "Copia l'indirizzo" }, @@ -332,6 +354,9 @@ "directDepositEtherExplainer": { "message": "Se possiedi già degli Ether, questa è la via più veloce per aggiungere Ether al tuo portafoglio con un deposito diretto." }, + "dismiss": { + "message": "Ignora" + }, "done": { "message": "Finito" }, @@ -497,6 +522,10 @@ "getStarted": { "message": "Inizia" }, + "greaterThanMin": { + "message": "deve essere maggiore o uguale a $1.", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "Siamo contenti di vederti." }, @@ -612,6 +641,10 @@ "ledgerAccountRestriction": { "message": "E' necessario utilizzare l'ultimo account prima di poterne aggiungere uno nuovo." }, + "lessThanMax": { + "message": "deve essere minore o uguale a $1.", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "Si, iniziamo!" }, @@ -800,6 +833,9 @@ "message": "Incolla la tua chiave privata qui:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Incolla la tua frase seed qui!" + }, "pending": { "message": "in corso" }, @@ -1113,6 +1149,9 @@ "storePhrase": { "message": "Conserva questa frase in un gestore di password come 1Password." }, + "submit": { + "message": "Invia" + }, "submitted": { "message": "Inviata" }, @@ -1277,6 +1316,9 @@ "userName": { "message": "Nome utente" }, + "validFileImport": { + "message": "Devi selezionare un file valido da importare." + }, "viewAccount": { "message": "Vedi Account" }, @@ -1325,6 +1367,24 @@ "zeroGasPriceOnSpeedUpError": { "message": "Prezzo del gas maggiore di zero" }, + "connections": { + "message": "Connessioni" + }, + "connectionsSettingsDescription": { + "message": "Siti autorizzati ad accedere ai tuoi accounts" + }, + "addSite": { + "message": "Aggiungi Sito" + }, + "addSiteDescription": { + "message": "Aggiungi un sito autorizzato ad accedere ai tuoi accounts, utile per dapps obsolete" + }, + "connected": { + "message": "Connesso" + }, + "connectedDescription": { + "message": "La lista di siti web autorizzati ad accedere ai tuoi indirizzi" + }, "contacts": { "message": "Contatti" }, diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json index 18dfb068e..5b85291b9 100644 --- a/app/_locales/ja/messages.json +++ b/app/_locales/ja/messages.json @@ -1,16 +1,28 @@ { + "privacyModeDefault": { + "message": "プライバシーモードがデフォルトで有効になりました" + }, "chartOnlyAvailableEth": { "message": "チャートはEthereumネットワークでのみ利用可能です。" }, + "confirmClear": { + "message": "承認されたウェブサイトをクリアしてもよろしいですか?" + }, "contractInteraction": { "message": "コントラクトへのアクセス" }, + "clearApprovalData": { + "message": "承認データのクリア" + }, "reject": { "message": "拒否" }, - "likeToConnect": { + "providerRequest": { "message": "$1 はあなたのアカウントにアクセスしようとしています。" }, + "providerRequestInfo": { + "message": "下記のドメインは、Ethereumブロックチェーンとやり取りできるようにEthereum APIへのアクセスをリクエストしようとしています。 Web3アクセスを承認する前に、正しいサイトにいることを常に確認してください。" + }, "aboutSettingsDescription": { "message": "バージョンやサポート、問合せ先など" }, @@ -72,7 +84,7 @@ "message": "推奨トークンを追加" }, "addAcquiredTokens": { - "message": "MetaMaskで獲得したトークンを追加する" + "message": "Metamaskで獲得したトークンを追加する" }, "amount": { "message": "金額" @@ -124,6 +136,10 @@ "balanceIsInsufficientGas": { "message": "現在のガス総量に対して残高が不足しています" }, + "betweenMinAndMax": { + "message": " $1以上 $2以下にして下さい。", + "description": "helper for inputting hex as decimal input" + }, "blockiesIdenticon": { "message": "Blockies Identicon を使用" }, @@ -157,6 +173,9 @@ "copiedExclamation": { "message": "コピー完了!" }, + "copy": { + "message": "コピー" + }, "copyToClipboard": { "message": "クリップボードへコピー" }, @@ -259,6 +278,10 @@ "message": "フォーセットで $1のEtherを得ることができます。", "description": "Displays network name for Ether faucet" }, + "greaterThanMin": { + "message": " $1以上にして下さい。", + "description": "helper for inputting hex as decimal input" + }, "here": { "message": "ここ", "description": "as in -click here- for more information (goes with troubleTokenBalances)" @@ -311,6 +334,10 @@ "learnMore": { "message": "詳細" }, + "lessThanMax": { + "message": " $1以下にして下さい。", + "description": "helper for inputting hex as decimal input" + }, "likeToAddTokens": { "message": "トークンを追加しますか?" }, @@ -389,6 +416,9 @@ "message": "秘密鍵をここにペーストして下さい:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "パスフレーズをここにペーストして下さい!" + }, "privacyMsg": { "message": "プライバシーポリシー" }, @@ -480,6 +510,9 @@ "sigRequest": { "message": "署名リクエスト" }, + "submit": { + "message": "送信" + }, "terms": { "message": "利用規約" }, diff --git a/app/_locales/kn/messages.json b/app/_locales/kn/messages.json index 7f3dd0f17..7fc114988 100644 --- a/app/_locales/kn/messages.json +++ b/app/_locales/kn/messages.json @@ -1,10 +1,19 @@ { + "privacyModeDefault": { + "message": "ಗೌಪ್ಯತೆ ಮೋಡ್ ಅನ್ನು ಡೀಫಾಲ್ಟ್‌ ಆಗಿ ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ" + }, "chartOnlyAvailableEth": { "message": "ಎಥೆರಿಯಮ್ ನೆಟ್‌ವರ್ಕ್‌ಗಳಲ್ಲಿ ಮಾತ್ರವೇ ಚಾರ್ಟ್‌ಗಳು ಲಭ್ಯವಿರುತ್ತವೆ." }, + "confirmClear": { + "message": "ನೀವು ಅನುಮೋದಿಸಿದ ವೆಬ್‌‌ಸೈಟ್‌ಗಳನ್ನು ತೆರವುಗೊಳಿಸಲು ಬಯಸುವಿರಾ?" + }, "contractInteraction": { "message": "ಒಪ್ಪಂದದ ಸಂವಹನ" }, + "clearApprovalData": { + "message": "ಗೌಪ್ಯತೆ ಡೇಟಾವನ್ನು ತೆರವುಗೊಳಿಸಿ" + }, "appName": { "message": "MetaMask", "description": "The name of the application" @@ -12,14 +21,17 @@ "reject": { "message": "ತಿರಸ್ಕರಿಸಿ" }, - "likeToConnect": { + "providerRequest": { "message": "$1 ನಿಮ್ಮ ಖಾತೆಗೆ ಸಂಪರ್ಕಿಸಲು ಬಯಸುತ್ತಿದೆ" }, + "providerRequestInfo": { + "message": "ಈ ಸೈಟ್ ನಿಮ್ಮ ಪ್ರಸ್ತುತ ಖಾತೆ ವಿಳಾಸವನ್ನು ವೀಕ್ಷಿಸಲು ಪ್ರವೇಶವನ್ನು ವಿನಂತಿಸುತ್ತಿದೆ. ನೀವು ಸಂವಹನ ನಡೆಸುವ ಸೈಟ್‌ಗಳನ್ನು ನೀವು ನಂಬಿರುವಿರಿ ಎಂಬುದನ್ನು ಯಾವಾಗಲೂ ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಿ." + }, "about": { "message": "ಕುರಿತು" }, "aboutSettingsDescription": { - "message": "ಆವೃತ್ತಿ, ಬೆಂಬಲ ಕೇಂದ್ರ ಮತ್ತು ಸಂಪರ್ಕ ಮಾಹಿತಿ" + "message": "ಆವೃತ್ತಿ, ಬೆಂಬಲ ಕೇಂದ್ರ ಮತ್ತು ಸಂಪರ್ಕ ಮಾಹಿತಿ." }, "acceleratingATransaction": { "message": "* ಹೆಚ್ಚಿನ ಗ್ಯಾಸ್ ಬೆಲೆಯನ್ನು ಬಳಸಿಕೊಂಡು ವಹಿವಾಟನ್ನು ವೇಗಗೊಳಿಸುವುದರಿಂದ ನೆಟ್‌ವರ್ಕ್ ವೇಗವಾಗಿ ಪ್ರಕ್ರಿಯೆಗೊಳ್ಳುವ ಸಾಧ್ಯತೆಗಳನ್ನು ಅದು ಹೆಚ್ಚಿಸುತ್ತದೆ, ಆದರೆ ಇದು ಯಾವಾಗಲೂ ಖಚಿತವಾಗಿರುವುದಿಲ್ಲ." @@ -55,7 +67,7 @@ "message": "ಸುಧಾರಿತ" }, "advancedSettingsDescription": { - "message": "ಡೆವಲಪರ್ ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ಪ್ರವೇಶಿಸಿ, ರಾಜ್ಯದ ಲಾಗ್‌ಗಳನ್ನು ಡೌನ್‌ಲೋಡ್ ಮಾಡಿ, ಖಾತೆಯನ್ನು ಮರುಹೊಂದಿಸಿ, ಟೆಸ್ಟ್‌ನೆಟ್ಸ್‌ ಹೊಂದಿಸಿ ಮತ್ತು ಕಸ್ಟಮ್ RPC" + "message": "ಡೆವಲಪರ್ ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ಪ್ರವೇಶಿಸಿ, ರಾಜ್ಯದ ಲಾಗ್‌ಗಳನ್ನು ಡೌನ್‌ಲೋಡ್ ಮಾಡಿ, ಖಾತೆಯನ್ನು ಮರುಹೊಂದಿಸಿ, ಟೆಸ್ಟ್‌ನೆಟ್ಸ್‌ ಹೊಂದಿಸಿ ಮತ್ತು ಕಸ್ಟಮ್ RPC." }, "advancedOptions": { "message": "ಸುಧಾರಿತ ಆಯ್ಕೆಗಳು" @@ -145,6 +157,10 @@ "basic": { "message": "ಮೂಲ" }, + "betweenMinAndMax": { + "message": " $1 ಗಿಂತಲೂ ಹೆಚ್ಚಾಗಿರಬೇಕು ಅಥವಾ ಸಮನಾಗಿರಬೇಕು ಮತ್ತು $2 ಗಿಂತಲೂ ಕಡಿಮೆ ಇರಬೇಕು ಅಥವಾ ಸಮನಾಗಿರಬೇಕು.", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerUrl": { "message": "ಅನ್ವೇಷಕವನ್ನು ನಿರ್ಬಂಧಿಸಿ" }, @@ -236,6 +252,9 @@ "connect": { "message": "ಸಂಪರ್ಕಿಸು" }, + "connectRequest": { + "message": "ವಿನಂತಿಯನ್ನು ಸಂಪರ್ಕಪಡಿಸಿ" + }, "connectingTo": { "message": "$1 ಗೆ ಸಂಪರ್ಕಪಡಿಸಲಾಗುತ್ತಿದೆ" }, @@ -275,6 +294,9 @@ "copiedExclamation": { "message": "ನಕಲಿಸಲಾಗಿದೆ!" }, + "copy": { + "message": "ನಕಲಿಸು" + }, "copyAddress": { "message": "ವಿಳಾಸವನ್ನು ಕ್ಲಿಪ್‌ಬೋರ್ಡ್‌ಗೆ ನಕಲಿಸಿ" }, @@ -353,6 +375,9 @@ "directDepositEtherExplainer": { "message": "ನೀವು ಈಗಾಗಲೇ ಕೆಲವು ಎಥರ್ ಹೊಂದಿದ್ದರೆ, ನೇರ ಠೇವಣಿ ಮೂಲಕ ನಿಮ್ಮ ಹೊಸ ವ್ಯಾಲೆಟ್‌ನಲ್ಲಿ ಎಥರ್ ಅನ್ನು ಪಡೆಯುವ ತ್ವರಿತ ಮಾರ್ಗ." }, + "dismiss": { + "message": "ವಜಾಗೊಳಿಸಿ" + }, "done": { "message": "ಮುಗಿದಿದೆ" }, @@ -518,6 +543,10 @@ "getStarted": { "message": "ಪ್ರಾರಂಭಗೊಂಡಿದೆ" }, + "greaterThanMin": { + "message": " $1 ಗಿಂತಲೂ ಹೆಚ್ಚಾಗಿರಬೇಕು ಅಥವಾ ಸಮನಾಗಿರಬೇಕು.", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "ನಿಮ್ಮನ್ನು ನೋಡಿ ನಮಗೆ ಸಂತೋಷವಾಗಿದೆ." }, @@ -636,6 +665,10 @@ "ledgerAccountRestriction": { "message": "ನೀವು ಹೊಸದನ್ನು ಸೇರಿಸುವುದರ ಮೊದಲು ನಿಮ್ಮ ಹಿಂದಿನ ಖಾತೆಯನ್ನು ನೀವು ಬಳಸಬೇಕು." }, + "lessThanMax": { + "message": "$1 ಗಿಂತ ಕಡಿಮೆ ಅಥವಾ ಸಮನಾಗಿರಬೇಕು.", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "ಹೌದು, ಹೊಂದಿಸೋಣ!" }, @@ -837,6 +870,9 @@ "message": "ನಿಮ್ಮ ಖಾಸಗಿ ಪ್ರಮುಖ ಸ್ಟ್ರಿಂಗ್ ಅನ್ನು ಇಲ್ಲಿ ನಕಲಿಸಿ:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "ನಿಮ್ಮ ಸೀಡ್ ಫ್ರೇಸ್ ಅನ್ನು ಇಲ್ಲಿ ನಕಲಿಸಿ!" + }, "pending": { "message": "ಬಾಕಿಯಿರುವುದು" }, @@ -1150,6 +1186,9 @@ "storePhrase": { "message": "ಈ ಫ್ರೇಸ್ ಅನ್ನು ಪಾಸ್‌ವರ್ಡ್ ನಿರ್ವಾಹಕದಲ್ಲಿ 1Password ರೂಪದಲ್ಲಿ ಸಂಗ್ರಹಿಸಿ." }, + "submit": { + "message": "ಸಲ್ಲಿಸು" + }, "submitted": { "message": "ಸಲ್ಲಿಸಲಾಗಿದೆ" }, @@ -1317,6 +1356,9 @@ "userName": { "message": "ಬಳಕೆದಾರಹೆಸರು" }, + "validFileImport": { + "message": "ನೀವು ಆಮದು ಮಾಡಲು ಮಾನ್ಯ ಫೈಲ್ ಅನ್ನು ಆಯ್ಕೆಮಾಡಬೇಕು." + }, "viewAccount": { "message": "ಖಾತೆಯನ್ನು ವೀಕ್ಷಿಸಿ" }, diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json index bd412c75c..c71e3c267 100644 --- a/app/_locales/ko/messages.json +++ b/app/_locales/ko/messages.json @@ -1,21 +1,33 @@ { + "privacyModeDefault": { + "message": "이제 프라이버시 모드가 기본 설정으로 활성화됐습니다" + }, "chartOnlyAvailableEth": { "message": "이더리움 네트워크에서만 사용 가능한 차트." }, + "confirmClear": { + "message": "승인 된 웹 사이트를 삭제 하시겠습니까?" + }, "contractInteraction": { "message": "계약 상호 작용" }, + "clearApprovalData": { + "message": "승인 데이터 삭제" + }, "reject": { "message": "거부" }, - "likeToConnect": { + "providerRequest": { "message": "$1이 당신의 계정에 연결하길 원합니다." }, + "providerRequestInfo": { + "message": "아래 나열된 도메인은 Web3 API에 대한 액세스를 요청하여 Ethereum 블록 체인과 상호 작용할 수 있습니다. Ethereum 액세스를 승인하기 전에 항상 올바른 사이트에 있는지 다시 확인하십시오." + }, "about": { "message": "정보" }, "aboutSettingsDescription": { - "message": "버전, 지원 센터, 그리고 연락처 정보" + "message": "버전, 지원 센터, 그리고 연락처 정보." }, "acceleratingATransaction": { "message": "* 더 높은 가스 요금을 사용하여 트랜잭션을 가속화하면 네트워크에 의해 더 빨리 처리될 가능성이 증가하지만 항상 빠른 처리가 보장되는 것은 아닙니다." @@ -51,7 +63,7 @@ "message": "고급" }, "advancedSettingsDescription": { - "message": "개발자 기능 사용, 상태 로그 다운로드, 계정 재설정, 테스트넷 및 사용자 정의 RPC 설정" + "message": "개발자 기능 사용, 상태 로그 다운로드, 계정 재설정, 테스트넷 및 사용자 정의 RPC 설정." }, "advancedOptions": { "message": "고급 옵션" @@ -142,6 +154,10 @@ "balanceIsInsufficientGas": { "message": "현재 가스 총합에 대해 잔액이 부족합니다" }, + "betweenMinAndMax": { + "message": "$1 이상 $2 이하여야 합니다.", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerUrl": { "message": "익스플로러 차단" }, @@ -233,6 +249,9 @@ "connect": { "message": "연결" }, + "connectRequest": { + "message": "연결 요청" + }, "connectingTo": { "message": "$1에 연결" }, @@ -272,6 +291,9 @@ "copiedExclamation": { "message": "복사됨!" }, + "copy": { + "message": "복사" + }, "copyAddress": { "message": "클립보드로 주소 복사" }, @@ -350,6 +372,9 @@ "directDepositEtherExplainer": { "message": "약간의 이더를 이미 보유하고 있다면, 새로 만든 지갑에 직접 입금하여 이더를 보유할 수 있습니다." }, + "dismiss": { + "message": "숨기기" + }, "done": { "message": "완료" }, @@ -515,6 +540,10 @@ "getStarted": { "message": "시작하기" }, + "greaterThanMin": { + "message": "$1 이상이어야 합니다.", + "description": "helper for inputting hex as decimal input" + }, "hardware": { "message": "하드웨어" }, @@ -630,6 +659,10 @@ "ledgerAccountRestriction": { "message": "새 계정을 추가하려면 최소 마지막 계정을 사용해야 합니다." }, + "lessThanMax": { + "message": "$1 이하여야합니다.", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "네, 설정해볼게요!" }, @@ -828,6 +861,9 @@ "message": "개인키를 입력해주세요:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "시드 구문을 이곳에 붙여넣어 주세요!" + }, "pending": { "message": "펜딩 중" }, @@ -1141,6 +1177,9 @@ "storePhrase": { "message": "이 구문을 1Password같은 암호 관리자에 저장하세요." }, + "submit": { + "message": "제출" + }, "submitted": { "message": "제출됨" }, @@ -1308,6 +1347,9 @@ "userName": { "message": "사용자이름" }, + "validFileImport": { + "message": "가져오기 위해 유효한 파일을 선택해야 합니다." + }, "viewAccount": { "message": "계정 보기" }, diff --git a/app/_locales/lt/messages.json b/app/_locales/lt/messages.json index c2a56ec54..7e029494f 100644 --- a/app/_locales/lt/messages.json +++ b/app/_locales/lt/messages.json @@ -1,21 +1,33 @@ { + "privacyModeDefault": { + "message": "Dabar privatumo režimas suaktyvintas pagal numatytąją nuostatą" + }, "chartOnlyAvailableEth": { "message": "Diagramos yra tik „Ethereum“ tinkluose." }, + "confirmClear": { + "message": "Ar tikrai norite panaikinti patvirtintas svetaines?" + }, "contractInteraction": { "message": "Sutartinė sąveika" }, + "clearApprovalData": { + "message": "Išvalyti asmeninius duomenis" + }, "reject": { "message": "Atmesti" }, - "likeToConnect": { + "providerRequest": { "message": "$1 norėtų prisijungti prie jūsų paskyros" }, + "providerRequestInfo": { + "message": "Ši svetainė prašo prieigos peržiūrėti jūsų dabartinės paskyros adresą. Visada patikrinkite, ar pasitikite svetainėmis, su kuriomis sąveikaujate." + }, "about": { "message": "Apie" }, "aboutSettingsDescription": { - "message": "Versija, palaikymo centras ir kontaktinė informacija" + "message": "Versija, palaikymo centras ir kontaktinė informacija." }, "acceleratingATransaction": { "message": "Operacijos paspartinimas naudojantis didesne dujų kaina padidina galimybes, kad ji bus greičiau apdorota tinkle, tačiau tai ne visada garantuojama. " @@ -51,7 +63,7 @@ "message": "Išplėstiniai" }, "advancedSettingsDescription": { - "message": "Prieigos kūrėjo funkcijos, būsenos žurnalų atsiuntimas, paskyros atstatymas, „testnet“ nustatymas ir pritaikytas RPC" + "message": "Prieigos kūrėjo funkcijos, būsenos žurnalų atsiuntimas, paskyros atstatymas, „testnet“ nustatymas ir pritaikytas RPC." }, "advancedOptions": { "message": "Išplėstinės parinktys" @@ -145,6 +157,10 @@ "basic": { "message": "Bendrieji" }, + "betweenMinAndMax": { + "message": "turi būti ne mažiau kaip $1 ir ne daugiau kaip $2.", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerUrl": { "message": "Blokuoti naršyklę" }, @@ -236,6 +252,9 @@ "connect": { "message": "Prisijungti" }, + "connectRequest": { + "message": "Prijungimo užklausa" + }, "connectingTo": { "message": "Jungiamasi prie $1" }, @@ -275,6 +294,9 @@ "copiedExclamation": { "message": "Nukopijuota!" }, + "copy": { + "message": "Kopijuoti" + }, "copyAddress": { "message": "Kopijuoti adresą į iškarpinę" }, @@ -353,6 +375,9 @@ "directDepositEtherExplainer": { "message": "Jeigu jau turite šiek tiek eterių, sparčiausias būdas gauti eterių į naują piniginę yra tiesioginis įnašas." }, + "dismiss": { + "message": "Atsisakyti" + }, "done": { "message": "Atlikta" }, @@ -518,6 +543,10 @@ "getStarted": { "message": "Darbo pradžia" }, + "greaterThanMin": { + "message": "turi būti daugiau arba lygu $1.", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "Džiaugiamės jus matydami." }, @@ -636,6 +665,10 @@ "ledgerAccountRestriction": { "message": "Prieš įtraukdami naują, turite pasinaudoti paskutine paskyra." }, + "lessThanMax": { + "message": "turi būti mažiau arba lygu $1.", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "Taip, pradėkime!" }, @@ -837,6 +870,9 @@ "message": "Čia įklijuokite asmeninio rakto eilutę:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Čia įklijuokite savo atkūrimo frazę!" + }, "pending": { "message": "laukiama patvirtinimo" }, @@ -1150,6 +1186,9 @@ "storePhrase": { "message": "Laikykite šią frazę slaptažodžių tvarkyklėje kaip 1 slaptažodį." }, + "submit": { + "message": "Pateikti" + }, "submitted": { "message": "Pateikta" }, @@ -1317,6 +1356,9 @@ "userName": { "message": "Vartotojo vardas" }, + "validFileImport": { + "message": "Turite pasirinkti galiojantį failą, kurį pageidaujate importuoti." + }, "viewAccount": { "message": "Žiūrėti paskyrą" }, diff --git a/app/_locales/lv/messages.json b/app/_locales/lv/messages.json index 0b5af9c4e..ace47600f 100644 --- a/app/_locales/lv/messages.json +++ b/app/_locales/lv/messages.json @@ -1,10 +1,19 @@ { + "privacyModeDefault": { + "message": "Privātais režīms tagad ieslēgts pēc noklusējuma" + }, "chartOnlyAvailableEth": { "message": "Grafiks pieejams vienīgi Ethereum tīklos." }, + "confirmClear": { + "message": "Vai tiešām vēlaties dzēst apstiprinātās vietnes?" + }, "contractInteraction": { "message": "Līguma mijiedarbības" }, + "clearApprovalData": { + "message": "Notīrīt konfidencialitātes datus" + }, "appName": { "message": "MetaMask", "description": "The name of the application" @@ -12,9 +21,12 @@ "reject": { "message": "Noraidīt" }, - "likeToConnect": { + "providerRequest": { "message": "$1 vēlas izveidot savienojumu ar jūsu kontu" }, + "providerRequestInfo": { + "message": "Šī lapa pieprasa piekļuvi jūsu pašreizēja konta adreses informācijai. Vienmēr pārliecinieties, ka uzticaties lapām, kuras apmeklējat." + }, "about": { "message": "Par" }, @@ -55,7 +67,7 @@ "message": "Papildu" }, "advancedSettingsDescription": { - "message": "Piekļūstiet izstrādātāju funkcijām, lejupielādējiet stāvokļu žurnālus, atiestatiet kontu, iestatiet testa tīklus un pielāgotos RPC izsaukumus" + "message": "Piekļūstiet izstrādātāju funkcijām, lejupielādējiet stāvokļu žurnālus, atiestatiet kontu, iestatiet testa tīklus un pielāgotos RPC izsaukumus." }, "advancedOptions": { "message": "Papildu opcijas" @@ -145,6 +157,10 @@ "basic": { "message": "Pamata" }, + "betweenMinAndMax": { + "message": "jābūt lielākai par vai vienādai ar $1 un mazākai par vai vienādai ar $2.", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerUrl": { "message": "Bloķēt Explorer" }, @@ -236,6 +252,9 @@ "connect": { "message": "Pievienošana" }, + "connectRequest": { + "message": "Savienojuma pieprasījums" + }, "connectingTo": { "message": "Pieslēdzas $1" }, @@ -275,6 +294,9 @@ "copiedExclamation": { "message": "Nokopēts!" }, + "copy": { + "message": "Kopēt" + }, "copyAddress": { "message": "Iekopēt adresi starpliktuvē" }, @@ -353,6 +375,9 @@ "directDepositEtherExplainer": { "message": "Ja jums jau ir Ether, tad visātrāk Ether savā makā varat saņemt ar tiešo iemaksu." }, + "dismiss": { + "message": "Noraidīt" + }, "done": { "message": "Pabeigts" }, @@ -514,6 +539,10 @@ "getStarted": { "message": "Sākt darbu" }, + "greaterThanMin": { + "message": "jābūt lielākam par vai vienādam ar $1.", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "Priecājamies jūs redzēt!" }, @@ -632,6 +661,10 @@ "ledgerAccountRestriction": { "message": "Jums jāizmanto pēdējais konts pirms varat pievienot jaunu." }, + "lessThanMax": { + "message": "jābūt mazākam par vai vienādam ar $1.", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "Jā, sāksim iestatīšanu!" }, @@ -833,6 +866,9 @@ "message": "Ielīmējiet privātās atslēgas rindu šeit:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Iekopējiet savu atkopšanas frāzi šeit!" + }, "pending": { "message": "gaida" }, @@ -1146,6 +1182,9 @@ "storePhrase": { "message": "Saglabājiet šo frāzi paroļu pārvaldniekā, piemēram, 1Password." }, + "submit": { + "message": "Iesniegt" + }, "submitted": { "message": "Iesniegts" }, @@ -1313,6 +1352,9 @@ "userName": { "message": "Lietotājvārds" }, + "validFileImport": { + "message": "Importēšanai jāatlasa derīga datne." + }, "viewAccount": { "message": "Skatīt kontu" }, diff --git a/app/_locales/ml/messages.json b/app/_locales/ml/messages.json index c898d1dc2..cbcee4186 100644 --- a/app/_locales/ml/messages.json +++ b/app/_locales/ml/messages.json @@ -42,6 +42,9 @@ "connect": { "message": "കണക്‌റ്റുചെയ്യുക" }, + "copy": { + "message": "പകര്‍ത്തുക" + }, "copyToClipboard": { "message": "ക്ലിപ്പ്ബോർഡിലേക്ക് പകർത്തുക" }, @@ -54,6 +57,9 @@ "details": { "message": "വിശദാംശങ്ങൾ‌" }, + "dismiss": { + "message": "ബഹിഷ്‌ക്കരിക്കുക" + }, "done": { "message": "പൂർത്തിയാക്കി" }, @@ -125,6 +131,9 @@ "settings": { "message": "ക്രമീകരണങ്ങള്‍" }, + "submit": { + "message": "സമര്‍പ്പിക്കൂ" + }, "tryAgain": { "message": "വീണ്ടും ശ്രമിക്കുക" }, diff --git a/app/_locales/mr/messages.json b/app/_locales/mr/messages.json index ef341f40c..af06c03fb 100644 --- a/app/_locales/mr/messages.json +++ b/app/_locales/mr/messages.json @@ -42,6 +42,9 @@ "connect": { "message": "कनेक्‍ट करा" }, + "copy": { + "message": "कॉपी करा" + }, "copyToClipboard": { "message": "क्लिपबोर्डवर कॉपी करा" }, @@ -54,6 +57,9 @@ "details": { "message": "तपशील" }, + "dismiss": { + "message": "डिसमिस करा" + }, "done": { "message": "पूर्ण झाले" }, @@ -125,6 +131,9 @@ "settings": { "message": "सेटिंग्ज" }, + "submit": { + "message": "सबमिट करा" + }, "tryAgain": { "message": "पुन्हा प्रयत्न करा" }, diff --git a/app/_locales/ms/messages.json b/app/_locales/ms/messages.json index 7c591b606..03f9ac0b2 100644 --- a/app/_locales/ms/messages.json +++ b/app/_locales/ms/messages.json @@ -1,21 +1,33 @@ { + "privacyModeDefault": { + "message": "Mod Privasi kini diaktifkan secara lalai" + }, "chartOnlyAvailableEth": { "message": "Carta hanya tersedia di rangkaian Ethereum." }, + "confirmClear": { + "message": "Adakah anda pasti mahu mengosongkan tapak web diluluskan?" + }, "contractInteraction": { "message": "Interaksi Kontrak" }, + "clearApprovalData": { + "message": "Kosongkan Data Privasi" + }, "reject": { "message": "Tolak" }, - "likeToConnect": { + "providerRequest": { "message": "$1 ingin menyambung kepada akaun anda" }, + "providerRequestInfo": { + "message": "Tapak ini meminta akses untuk melihat alamat akaun semasa anda. Sentiasa pastikan anda mempercayai tapak web yang anda berinteraksi." + }, "about": { "message": "Mengenai" }, "aboutSettingsDescription": { - "message": "Versi, pusat sokongan, dan maklumat perhubungan" + "message": "Versi, pusat sokongan, dan maklumat perhubungan." }, "acceleratingATransaction": { "message": "* Mempercepatkan transaksi menggunakan harga gas lebih tinggi akan meningkatkan peluang diproses oleh rangkaian lebih cepat, tetapi ini pun tidak sentiasa dijamin." @@ -51,7 +63,7 @@ "message": "Lanjutan" }, "advancedSettingsDescription": { - "message": "Akses ciri-ciri pembangun, muat turun Log Keadaan, Set Semula Akaun, sediakan jaringan ujian dan RPC tersuai" + "message": "Akses ciri-ciri pembangun, muat turun Log Keadaan, Set Semula Akaun, sediakan jaringan ujian dan RPC tersuai." }, "advancedOptions": { "message": "Pilihan Lanjutan" @@ -145,6 +157,10 @@ "basic": { "message": "Asas" }, + "betweenMinAndMax": { + "message": "mestilah lebih besar atau sama dengan $1 dan kurang atau sama dengan $2.", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerUrl": { "message": "Sekat Explorer" }, @@ -233,6 +249,9 @@ "connect": { "message": "Sambung" }, + "connectRequest": { + "message": "Sambungkan Permintaan" + }, "connectingTo": { "message": "Menyambungkan kepada $1" }, @@ -272,6 +291,9 @@ "copiedExclamation": { "message": "Disalin!" }, + "copy": { + "message": "Salin" + }, "copyAddress": { "message": "Salin alamat kepada papan klip" }, @@ -344,6 +366,9 @@ "directDepositEtherExplainer": { "message": "Jika anda sudah mempunyai Ether, cara paling cepat untuk mendapatkan Ether di dompet baru anda ialah dengan deposit langsung." }, + "dismiss": { + "message": "Singkirkan" + }, "done": { "message": "Selesai" }, @@ -502,6 +527,10 @@ "getStarted": { "message": "Bermula" }, + "greaterThanMin": { + "message": "mestilah melebihi atau bersamaan dengan $1.", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "Kami gembira bertemu anda." }, @@ -616,6 +645,10 @@ "ledgerAccountRestriction": { "message": "Anda perlu menggunakan akaun terakhir anda sebelum anda boleh menambah yang baru." }, + "lessThanMax": { + "message": "mestilah kurang atau bersamaan dengan $1.", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "Ya, mari sediakannya!" }, @@ -808,6 +841,9 @@ "message": "Tampal rentetan kekunci persendirian anda di sini:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Tampal ungkapan benih anda di sini!" + }, "pending": { "message": "menunggu" }, @@ -1121,6 +1157,9 @@ "storePhrase": { "message": "Simpan ungkapan ini di dalam pengurus kata laluan seperti 1Password." }, + "submit": { + "message": "Serah" + }, "submitted": { "message": "Dihantar" }, @@ -1285,6 +1324,9 @@ "userName": { "message": "Nama pengguna" }, + "validFileImport": { + "message": "Anda mesti pilih fail yang sah untuk diimport." + }, "viewAccount": { "message": "Paparkan Akaun" }, diff --git a/app/_locales/nl/messages.json b/app/_locales/nl/messages.json index 7ba285208..ba83e37a1 100644 --- a/app/_locales/nl/messages.json +++ b/app/_locales/nl/messages.json @@ -1,7 +1,16 @@ { + "confirmClear": { + "message": "Weet je zeker dat je goedgekeurde websites wilt wissen?" + }, + "clearApprovalData": { + "message": "Gegevens over goedkeuring wissen" + }, "reject": { "message": "Afwijzen" }, + "providerRequestInfo": { + "message": "Het onderstaande domein probeert toegang tot de Ethereum API te vragen zodat deze kan communiceren met de Ethereum-blockchain. Controleer altijd eerst of u op de juiste site bent voordat u Ethereum-toegang goedkeurt." + }, "accountDetails": { "message": "Accountgegevens" }, @@ -43,6 +52,10 @@ "balanceIsInsufficientGas": { "message": "Onvoldoende saldo voor huidig ​​gastotaal" }, + "betweenMinAndMax": { + "message": "moet groter zijn dan of gelijk zijn aan $1 en kleiner dan of gelijk aan $2.", + "description": "helper for inputting hex as decimal input" + }, "blockiesIdenticon": { "message": "Gebruik Blockies Identicon" }, @@ -79,6 +92,9 @@ "copiedExclamation": { "message": "Gekopieerde!" }, + "copy": { + "message": "Kopiëren" + }, "copyToClipboard": { "message": "Kopieer naar klembord" }, @@ -181,6 +197,10 @@ "message": "Haal Ether uit een kraan voor de $1", "description": "Displays network name for Ether faucet" }, + "greaterThanMin": { + "message": "moet groter zijn dan of gelijk zijn aan $1.", + "description": "helper for inputting hex as decimal input" + }, "here": { "message": "hier", "description": "as in -click here- for more information (goes with troubleTokenBalances)" @@ -239,6 +259,10 @@ "kovan": { "message": "Kovan-testnetwerk" }, + "lessThanMax": { + "message": "moet kleiner zijn dan of gelijk zijn aan $1.", + "description": "helper for inputting hex as decimal input" + }, "likeToAddTokens": { "message": "Wil je deze tokens toevoegen?" }, @@ -307,6 +331,9 @@ "message": "Plak hier uw privésleutelstring:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Plak je back-up woorden hier!" + }, "personalAddressDetected": { "message": "Persoonlijk adres gedetecteerd. Voer het tokencontractadres in." }, @@ -410,6 +437,9 @@ "stateLogsDescription": { "message": "Staatslogboeken bevatten uw openbare accountadressen en verzonden transacties." }, + "submit": { + "message": "voorleggen" + }, "supportCenter": { "message": "Bezoek ons ​​ondersteuningscentrum" }, @@ -454,6 +484,9 @@ "usedByClients": { "message": "Gebruikt door verschillende klanten" }, + "validFileImport": { + "message": "U moet een geldig bestand selecteren om te importeren." + }, "viewAccount": { "message": "Bekijk account" }, diff --git a/app/_locales/no/messages.json b/app/_locales/no/messages.json index 6617284b7..83a6f09d3 100644 --- a/app/_locales/no/messages.json +++ b/app/_locales/no/messages.json @@ -1,21 +1,33 @@ { + "privacyModeDefault": { + "message": "Personvernmodus er nå aktivert som standard" + }, "chartOnlyAvailableEth": { "message": "Diagram kun tilgjengelig på Ethereum-nettverk." }, + "confirmClear": { + "message": "Er du sikker på at du vil tømme godkjente nettsteder?" + }, "contractInteraction": { "message": "Kontraktssamhandling" }, + "clearApprovalData": { + "message": "Tøm personvernsdata" + }, "reject": { "message": "Avslå" }, - "likeToConnect": { + "providerRequest": { "message": "$1 ønsker å forbindes med kontoen din " }, + "providerRequestInfo": { + "message": "Dette nettstedet ber om tilgang til å vise din nåværende kontoadresse. Alltid kontroller at du stoler på nettstedene du samhandler med." + }, "about": { "message": "Info" }, "aboutSettingsDescription": { - "message": "Versjon, brukerstøtte og kontaktinformasjon" + "message": "Versjon, brukerstøtte og kontaktinformasjon." }, "acceleratingATransaction": { "message": "* Akselerering av en transaksjon ved å bruke en høyere datakraftspris øker sjansene for å bli behandlet av nettverket raskere, men det er ikke alltid garantert." @@ -51,7 +63,7 @@ "message": "Avansert" }, "advancedSettingsDescription": { - "message": "Få tilgang til utviklerfunksjoner, last ned tilstandslogger, tilbakestill konto, installer testnett og tilpasset RPC" + "message": "Få tilgang til utviklerfunksjoner, last ned tilstandslogger, tilbakestill konto, installer testnett og tilpasset RPC." }, "advancedOptions": { "message": "Avanserte valg" @@ -145,6 +157,10 @@ "basic": { "message": "Enkle" }, + "betweenMinAndMax": { + "message": "må være større enn eller det samme som $1 og mindre enn eller det samme som $2. ", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerUrl": { "message": "Blokkér Explorer" }, @@ -233,6 +249,9 @@ "connect": { "message": "Koble til" }, + "connectRequest": { + "message": "Kontaktforespørsel " + }, "connectingTo": { "message": "Forbinder til $1 " }, @@ -272,6 +291,9 @@ "copiedExclamation": { "message": "Kopiert!" }, + "copy": { + "message": "Kopier" + }, "copyAddress": { "message": "Kopier adresse til utklippstavlen " }, @@ -350,6 +372,9 @@ "directDepositEtherExplainer": { "message": "Hvis du allerede har noe Ether, er den raskeste måten å få Ether i den nye lommeboken din på ved hjelp av direkte innskudd." }, + "dismiss": { + "message": "Lukk" + }, "done": { "message": "Ferdig" }, @@ -511,6 +536,10 @@ "getStarted": { "message": "Kom i gang" }, + "greaterThanMin": { + "message": "Må være mer enn eller tilsvarende $1.", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "Vi er glade for å se deg " }, @@ -626,6 +655,10 @@ "ledgerAccountRestriction": { "message": "Du må bruke den siste kontoen din før du kan legge til en ny." }, + "lessThanMax": { + "message": "Må være mindre eller likt som $1.", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "Ja, la oss komme i gang!" }, @@ -827,6 +860,9 @@ "message": "Lim inn din private nøkkelstreng her:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Lim inn seed-frasen din her!" + }, "pending": { "message": "i påvente " }, @@ -1128,6 +1164,9 @@ "storePhrase": { "message": "Lagre denne frasen i en passordbehandler slik som 1Password." }, + "submit": { + "message": "Send" + }, "submitted": { "message": "Sendt inn" }, @@ -1289,6 +1328,9 @@ "userName": { "message": "Brukernavn" }, + "validFileImport": { + "message": "Du må velge en gyldig fil å importere" + }, "viewAccount": { "message": "Se konto" }, diff --git a/app/_locales/ph/messages.json b/app/_locales/ph/messages.json index 1a3807ad5..a3df5d4ed 100644 --- a/app/_locales/ph/messages.json +++ b/app/_locales/ph/messages.json @@ -1,4 +1,10 @@ { + "confirmClear": { + "message": "Sigurado ka bang gusto mong i-clear ang mga naaprubahang website?" + }, + "clearApprovalData": { + "message": "Tanggalin ang data ng pag-apruba" + }, "appName": { "message": "MetaMask", "description": "The name of the application" @@ -9,6 +15,9 @@ "reject": { "message": "Tanggihan" }, + "providerRequestInfo": { + "message": "Ang domain na nakalista sa ibaba ay sinusubukang humiling ng access sa Ethereum API upang maaari itong makipag-ugnayan sa Ethereum blockchain. Laging i-double check na ikaw ay nasa tamang site bago aprubahan ang Ethereum access." + }, "accountDetails": { "message": "Detalye ng Account" }, @@ -37,6 +46,10 @@ "balanceIsInsufficientGas": { "message": "Kulang ang balanse para sa kasalukuyang gas total" }, + "betweenMinAndMax": { + "message": "dapat mas malaki o katumbas ng $1 at mas mababa o katumbas ng $2.", + "description": "helper para sa pag-input ng hex bilang decimal input" + }, "buyCoinSwitch": { "message": "Bumili sa CoinSwitch" }, @@ -67,6 +80,9 @@ "copiedExclamation": { "message": "Kinopya!" }, + "copy": { + "message": "Kinopya" + }, "copyToClipboard": { "message": "Kinopya sa clipboard" }, @@ -151,6 +167,10 @@ "message": "Kumuha ng Ether mula sa faucet para sa $1", "description": "Ipinapakita ang pangalan ng network para sa Ether faucet" }, + "greaterThanMin": { + "message": "dapat mas malaki o katumbas ng $1.", + "description": "helper para sa pag-input ng hex bilang decimal input" + }, "here": { "message": "i-click ito", "description": "tulad ng -i-click dito- para sa mas maraming impormasyon (kasama ng troubleTokenBalances)" @@ -184,6 +204,10 @@ "invalidInput": { "message": "Invalid ang input." }, + "lessThanMax": { + "message": "dapat mas mababa o katumbas ng $1.", + "description": "helper para sa pag-input ng hex bilang decimal input" + }, "loading": { "message": "Naglo-load..." }, @@ -228,6 +252,9 @@ "message": "I-paste dito ang iyong private key string:", "description": "Para sa pag-import ng account mula sa private key" }, + "pasteSeed": { + "message": "I-paste dito ang iyong seed phrase!" + }, "privateKeyWarning": { "message": "Babala: Huwag sabihin sa kahit na sino ang key na ito. Maaring makuha at manakaw ng sinumang nakakaalam ng iyong private key ang mga assets sa iyong account." }, @@ -276,6 +303,9 @@ "sigRequest": { "message": "Hiling na Signature" }, + "submit": { + "message": "I-submit" + }, "toETHviaShapeShift": { "message": "$1 sa ETH sa pamamagitan ng ShapeShift", "description": "Pupunan ng system ang deposit type sa simula ng mensahe" diff --git a/app/_locales/pl/messages.json b/app/_locales/pl/messages.json index 2441408f6..5ffd5585a 100644 --- a/app/_locales/pl/messages.json +++ b/app/_locales/pl/messages.json @@ -1,21 +1,33 @@ { + "privacyModeDefault": { + "message": "Tryb prywatny jest domyślnie włączony" + }, "chartOnlyAvailableEth": { "message": "Wykres dostępny tylko w sieciach Ethereum" }, + "confirmClear": { + "message": "Czy na pewno chcesz usunąć zatwierdzone strony internetowe?" + }, "contractInteraction": { "message": "Interakcja z kontraktem" }, + "clearApprovalData": { + "message": "Usuń dane poufne" + }, "reject": { "message": "Odrzuć" }, - "likeToConnect": { + "providerRequest": { "message": "$1 chce połączyć się z Twoim kontem" }, + "providerRequestInfo": { + "message": "Ta strona prosi o dostęp, aby zobaczyć adres Twojego aktualnego konta. Zawsze upewnij się, że ufasz stronom, z którymi wchodzisz w interakcję." + }, "about": { "message": "Informacje" }, "aboutSettingsDescription": { - "message": "Wersja, centrum wsparcia i dane kontaktowe" + "message": "Wersja, centrum wsparcia i dane kontaktowe." }, "acceleratingATransaction": { "message": "* Przyspieszenie transakcji poprzez zastosowanie wyższej ceny gazu zwiększa szanse na jej szybsze przetworzenie przez sieć, jednak skuteczność tej operacji nie jest gwarantowana." @@ -51,7 +63,7 @@ "message": "Zaawansowane" }, "advancedSettingsDescription": { - "message": "Dostęp do funkcji programisty, pobieranie dzienników stanu, resetowanie konta, konfigurowanie sieci testowych i niestandardowe RPC" + "message": "Dostęp do funkcji programisty, pobieranie dzienników stanu, resetowanie konta, konfigurowanie sieci testowych i niestandardowe RPC." }, "advancedOptions": { "message": "Opcje zaawansowane" @@ -145,6 +157,10 @@ "basic": { "message": "Podstawy" }, + "betweenMinAndMax": { + "message": "musi być większe lub równe $1 i mniejsze lub równe $2,", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerUrl": { "message": "Przeglądaj blok" }, @@ -233,6 +249,9 @@ "connect": { "message": "Połącz" }, + "connectRequest": { + "message": "Potwierdź żądanie" + }, "connectingTo": { "message": "Łączenie z $1" }, @@ -272,6 +291,9 @@ "copiedExclamation": { "message": "Skopiowane!" }, + "copy": { + "message": "Skopiuj" + }, "copyAddress": { "message": "Skopiuj adres do schowka" }, @@ -350,6 +372,9 @@ "directDepositEtherExplainer": { "message": "Jeśli już masz Eter, najszybciej umieścisz go w swoim nowym portfelu przy pomocy bezpośredniego depozytu." }, + "dismiss": { + "message": "Zamknij" + }, "done": { "message": "Gotowe" }, @@ -515,6 +540,10 @@ "getStarted": { "message": "Rozpocznij" }, + "greaterThanMin": { + "message": "musi być większe lub równe $1.", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "Cieszymy się, że tu jesteś." }, @@ -633,6 +662,10 @@ "ledgerAccountRestriction": { "message": "Musisz użyć swojego poprzedniego konta zanim dodasz kolejne." }, + "lessThanMax": { + "message": "musi być mniejsze lub równe $1.", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "Tak, zacznijmy od początku!" }, @@ -828,6 +861,9 @@ "message": "Tutaj wklej swój prywatny klucz:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Tutaj wklej swoją frazę seed!" + }, "pending": { "message": "oczekiwanie" }, @@ -1141,6 +1177,9 @@ "storePhrase": { "message": "Przechowuj tę frazę w menedżerze haseł, takim jak 1Password." }, + "submit": { + "message": "Wyślij" + }, "submitted": { "message": "Wysłane" }, @@ -1302,6 +1341,9 @@ "userName": { "message": "Nazwa użytkownika" }, + "validFileImport": { + "message": "Należy wybrać prawidłowy plik do zaimportowania." + }, "viewAccount": { "message": "Zobacz konto" }, diff --git a/app/_locales/pt/messages.json b/app/_locales/pt/messages.json index 9d9ed0450..70c2e52ae 100644 --- a/app/_locales/pt/messages.json +++ b/app/_locales/pt/messages.json @@ -1,7 +1,16 @@ { + "confirmClear": { + "message": "Tem certeza de que deseja limpar sites aprovados?" + }, + "clearApprovalData": { + "message": "Limpar dados de aprovação" + }, "reject": { "message": "Rejeitar" }, + "providerRequestInfo": { + "message": "O domínio listado abaixo está tentando solicitar acesso à API Ethereum para que ele possa interagir com o blockchain Ethereum. Sempre verifique se você está no site correto antes de aprovar o acesso à Ethereum." + }, "account": { "message": "Conta" }, @@ -46,6 +55,10 @@ "balanceIsInsufficientGas": { "message": "Saldo insuficiente para a quantidade de gas total" }, + "betweenMinAndMax": { + "message": "tem de ser maior ou igual a $1 e menor ou igual a $2.", + "description": "helper for inputting hex as decimal input" + }, "blockiesIdenticon": { "message": "Usar Blockies Identicon" }, @@ -82,6 +95,9 @@ "copiedExclamation": { "message": "Copiado!" }, + "copy": { + "message": "Copiar" + }, "copyToClipboard": { "message": "Copiar para o clipboard" }, @@ -187,6 +203,10 @@ "message": "Obter Ether de um faucet por $1", "description": "Displays network name for Ether faucet" }, + "greaterThanMin": { + "message": "tem de ser maior ou igual a $1.", + "description": "helper for inputting hex as decimal input" + }, "here": { "message": "aqui", "description": "as in -click here- for more information (goes with troubleTokenBalances)" @@ -245,6 +265,10 @@ "kovan": { "message": "Rede de Teste Kovan" }, + "lessThanMax": { + "message": "tem de ser menor ou igual a $1.", + "description": "helper for inputting hex as decimal input" + }, "likeToAddTokens": { "message": "Gostaria de adicionar estes tokens?" }, @@ -317,6 +341,9 @@ "message": "Cole aqui a sua chave privada:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Cole aqui a sua frase seed!" + }, "personalAddressDetected": { "message": "Endereço pessoal detectado. Introduza o endereço do contrato do token." }, @@ -420,6 +447,9 @@ "stateLogsDescription": { "message": "Registo de estado podem conter o seu endereço e transações enviadas da sua conta pública." }, + "submit": { + "message": "Submeter" + }, "supportCenter": { "message": "Visitar o nosso Centro de Suporte" }, @@ -464,6 +494,9 @@ "usedByClients": { "message": "Utilizado por vários tipos de clientes" }, + "validFileImport": { + "message": "Deve selecionar um ficheiro válido para importar." + }, "viewAccount": { "message": "Ver Conta" }, diff --git a/app/_locales/pt_BR/messages.json b/app/_locales/pt_BR/messages.json index e2de06a19..ecb223dcb 100644 --- a/app/_locales/pt_BR/messages.json +++ b/app/_locales/pt_BR/messages.json @@ -1,10 +1,19 @@ { + "privacyModeDefault": { + "message": "O Modo de Privacidade está ativado por padrão" + }, "chartOnlyAvailableEth": { "message": "Tabela disponível apenas em redes de Ethereum." }, + "confirmClear": { + "message": "Tem certeza de que deseja limpar os sites aprovados?" + }, "contractInteraction": { "message": "Interação do Contrato" }, + "clearApprovalData": { + "message": "Limpar Dados de Privacidade" + }, "appName": { "message": "MetaMask", "description": "The name of the application" @@ -12,14 +21,17 @@ "reject": { "message": "Rejeitar" }, - "likeToConnect": { + "providerRequest": { "message": "$1 gostaria de se conectar à sua conta" }, + "providerRequestInfo": { + "message": "Este site está solicitando acesso para visualizar o seu endereço de conta atual. Certifique-se sempre de confiar nos sites com os quais você interage." + }, "about": { "message": "Sobre" }, "aboutSettingsDescription": { - "message": "Versão, centro de apoio e informações de contato" + "message": "Versão, centro de apoio e informações de contato." }, "acceleratingATransaction": { "message": "* Acelerar uma transação usando um preço de gás mais alto aumenta suas chances de a rede processá-la de forma mais rápida, mas isso nem sempre é garantido." @@ -55,7 +67,7 @@ "message": "Avançado" }, "advancedSettingsDescription": { - "message": "Acesse recursos do desenvolvedor, baixe registros de estado, redefina a conta, configure testnets e personalize RPC" + "message": "Acesse recursos do desenvolvedor, baixe registros de estado, redefina a conta, configure testnets e personalize RPC." }, "advancedOptions": { "message": "Opções avançadas" @@ -145,6 +157,10 @@ "basic": { "message": "Básicas" }, + "betweenMinAndMax": { + "message": "deve ser maior ou igual a $1 e menor ou igual a $2.", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerView": { "message": "Ver conta em $1", "description": "$1 replaced by URL for custom block explorer" @@ -230,6 +246,9 @@ "connect": { "message": "Conectar-se" }, + "connectRequest": { + "message": "Solicitação de Conexão" + }, "connectingTo": { "message": "Conectando a $1" }, @@ -269,6 +288,9 @@ "copiedExclamation": { "message": "Copiado!" }, + "copy": { + "message": "Copiar" + }, "copyAddress": { "message": "Copiar endereço para a área de transferência" }, @@ -347,6 +369,9 @@ "directDepositEtherExplainer": { "message": "Se você já tem Ether, a forma mais rápida de colocá-lo em sua nova carteira é o depósito direto." }, + "dismiss": { + "message": "Dispensar" + }, "done": { "message": "Concluído" }, @@ -512,6 +537,10 @@ "getStarted": { "message": "Primeiros passos" }, + "greaterThanMin": { + "message": "deve ser maior ou igual a $1.", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "Estamos felizes em vê-lo." }, @@ -630,6 +659,10 @@ "ledgerAccountRestriction": { "message": "Você precisa usar sua última conta antes de adicionar uma nova." }, + "lessThanMax": { + "message": "deve ser inferior ou igual a $1.", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "Sim, vamos configurar!" }, @@ -822,6 +855,9 @@ "message": "Cole a string de sua chave particular aqui:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Cole sua seed phrase aqui!" + }, "pending": { "message": "pendente" }, @@ -1135,6 +1171,9 @@ "storePhrase": { "message": "Guarde esta frase em um gerenciador de senhas como o 1Password." }, + "submit": { + "message": "Enviar" + }, "submitted": { "message": "Enviado" }, @@ -1296,6 +1335,9 @@ "userName": { "message": "Nome de usuário" }, + "validFileImport": { + "message": "Você precisa selecionar um arquivo válido para importar." + }, "viewAccount": { "message": "Visualizar conta" }, diff --git a/app/_locales/pt_PT/messages.json b/app/_locales/pt_PT/messages.json index ae85eb26c..ce35721de 100644 --- a/app/_locales/pt_PT/messages.json +++ b/app/_locales/pt_PT/messages.json @@ -48,6 +48,9 @@ "copiedExclamation": { "message": "Copiado!" }, + "copy": { + "message": "Copiar" + }, "copyToClipboard": { "message": "Copiar para a área de transferência" }, @@ -63,6 +66,9 @@ "details": { "message": "Detalhes" }, + "dismiss": { + "message": "Ignorar" + }, "done": { "message": "Concluído" }, @@ -150,6 +156,9 @@ "settings": { "message": "Definições" }, + "submit": { + "message": "Submeter" + }, "tips": { "message": "Doações" }, diff --git a/app/_locales/ro/messages.json b/app/_locales/ro/messages.json index 3ed11cb86..5fed700da 100644 --- a/app/_locales/ro/messages.json +++ b/app/_locales/ro/messages.json @@ -1,21 +1,33 @@ { + "privacyModeDefault": { + "message": "Modul de confidențialitate este activat acum în mod implicit" + }, "chartOnlyAvailableEth": { "message": "Grafic disponibil numai pe rețelele Ethereum." }, + "confirmClear": { + "message": "Sunteți sigur că doriți să ștergeți site-urile aprobate?" + }, "contractInteraction": { "message": "Interacțiune contract" }, + "clearApprovalData": { + "message": "Ștergeți datele confidențiale" + }, "reject": { "message": "Respingeți" }, - "likeToConnect": { + "providerRequest": { "message": "$1 ar dori să se conecteze la contul dvs." }, + "providerRequestInfo": { + "message": "Acest site solicită acces pentru a vedea adresa curentă a contului dvs. Asigurați-vă întotdeauna că aveți încredere în site-urile cu care interacționați." + }, "about": { "message": "Despre" }, "aboutSettingsDescription": { - "message": "Versiune, centru de asistență și date de contact" + "message": "Versiune, centru de asistență și date de contact." }, "acceleratingATransaction": { "message": "* Accelerarea unei tranzacții folosind un preț în gas mai mare îi crește șansele de a fi procesată mai rapid de rețea, însă acest lucru nu este garantat întotdeauna." @@ -51,7 +63,7 @@ "message": "Avansate" }, "advancedSettingsDescription": { - "message": "Accesați funcții pentru dezvoltatori, descărcați Jurnale de stare, resetați contul, configurați rețele de test și RPC personalizat" + "message": "Accesați funcții pentru dezvoltatori, descărcați Jurnale de stare, resetați contul, configurați rețele de test și RPC personalizat." }, "advancedOptions": { "message": "Opțiuni avansate" @@ -145,6 +157,10 @@ "basic": { "message": "De bază" }, + "betweenMinAndMax": { + "message": "trebuie să fie mai mare sau egal cu $1 și mai mic sau egal cu $2.", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerUrl": { "message": "Explorator blocuri" }, @@ -236,6 +252,9 @@ "connect": { "message": "Conectează-te" }, + "connectRequest": { + "message": "Solicitare de conectare" + }, "connectingTo": { "message": "Se conectează la $1" }, @@ -275,6 +294,9 @@ "copiedExclamation": { "message": "Copiat!" }, + "copy": { + "message": "Copiază" + }, "copyAddress": { "message": "Copiere adresă în clipboard" }, @@ -353,6 +375,9 @@ "directDepositEtherExplainer": { "message": "Dacă deja aveți Ether, cel mai rapid mod de a avea Ether în portofelul nou prin depunere directă." }, + "dismiss": { + "message": "Închide" + }, "done": { "message": "Efectuat" }, @@ -511,6 +536,10 @@ "getStarted": { "message": "Începe" }, + "greaterThanMin": { + "message": "trebuie să fie mai mare sau egal cu $1.", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "Ne pare bine să vă vedem." }, @@ -626,6 +655,10 @@ "ledgerAccountRestriction": { "message": "Trebuie să folosiți ultimul cont înainte să adăugați altul." }, + "lessThanMax": { + "message": "trebuie să fie mai mic sau egal cu $1.", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "Da, hai să configurăm!" }, @@ -824,6 +857,9 @@ "message": "Lipiți aici șirul de chei private:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Copiați-vă expresia seed aici!" + }, "pending": { "message": "în așteptare" }, @@ -1137,6 +1173,9 @@ "storePhrase": { "message": "Păstrați această expresie într-un program de gestionare a parolelor cum ar fi 1Password." }, + "submit": { + "message": "Trimite" + }, "submitted": { "message": "Trimis" }, @@ -1298,6 +1337,9 @@ "userName": { "message": "Nume utilizator" }, + "validFileImport": { + "message": "Trebuie să selectați un fișier valabil pentru importare." + }, "viewAccount": { "message": "Afișați contul" }, diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json index 43249d6bc..082e450da 100644 --- a/app/_locales/ru/messages.json +++ b/app/_locales/ru/messages.json @@ -1,7 +1,16 @@ { + "confirmClear": { + "message": "Вы уверены, что хотите очистить утвержденные веб-сайты?Tem certeza de que deseja limpar sites aprovados?" + }, + "clearApprovalData": { + "message": "Четкие данные об утверждении" + }, "reject": { "message": "Отклонить" }, + "providerRequestInfo": { + "message": "Домен, указанный ниже, пытается запросить доступ к API-интерфейсу Ethereum, чтобы он мог взаимодействовать с блокчейном Ethereum. Всегда проверяйте, что вы находитесь на правильном сайте, прежде чем одобрять доступ к веб-сайту." + }, "account": { "message": "Счет" }, @@ -49,6 +58,10 @@ "balanceIsInsufficientGas": { "message": "Недостаточный баланс для текущего объема газа" }, + "betweenMinAndMax": { + "message": "должно быть больше или равно $1 и меньше или равно $2.", + "description": "helper for inputting hex as decimal input" + }, "blockiesIdenticon": { "message": "Использовать Blockies Identicon" }, @@ -103,6 +116,9 @@ "copiedExclamation": { "message": "Скопировано!" }, + "copy": { + "message": "Скопировать" + }, "copyToClipboard": { "message": "Скопировать в буфер обмена" }, @@ -214,6 +230,10 @@ "message": "Получить Ether из крана для $1", "description": "Displays network name for Ether faucet" }, + "greaterThanMin": { + "message": "должно быть больше или равно $1.", + "description": "helper for inputting hex as decimal input" + }, "here": { "message": "тут", "description": "as in -click here- for more information (goes with troubleTokenBalances)" @@ -278,6 +298,10 @@ "learnMore": { "message": "Узнать больше." }, + "lessThanMax": { + "message": "должно быть меньше или равно $1.", + "description": "helper for inputting hex as decimal input" + }, "likeToAddTokens": { "message": "Вы хотите добавить эти токены?" }, @@ -353,6 +377,9 @@ "message": "Вставьте ваш закрытый ключ тут:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Вставьте вашу ключевую фразу!" + }, "personalAddressDetected": { "message": "Обнаружен персональный адрес. Введите адрес контракта токена." }, @@ -465,6 +492,9 @@ "stateLogError": { "message": "Ошибка при получении журнала состояния." }, + "submit": { + "message": "Отправить" + }, "submitted": { "message": "Отправлена" }, @@ -518,6 +548,9 @@ "usedByClients": { "message": "Используется различными клиентами" }, + "validFileImport": { + "message": "Вам нужно выбрать правильный файл для импорта." + }, "viewAccount": { "message": "Посмотреть счет" }, @@ -536,13 +569,16 @@ "youSign": { "message": "Вы подписываете" }, + "privacyModeDefault": { + "message": "Режим конфиденциальности теперь включен по умолчанию" + }, "chartOnlyAvailableEth": { "message": "Диаграмма доступна только в сетях Ethereum." }, "contractInteraction": { "message": "Взаимодействие с контрактом" }, - "likeToConnect": { + "providerRequest": { "message": "$1 запрашивает доступ к вашему аккаунту" }, "about": { @@ -695,6 +731,9 @@ "connect": { "message": "Подключиться" }, + "connectRequest": { + "message": "Запрос на подключение" + }, "connectingTo": { "message": "Подключение к $1" }, @@ -737,6 +776,9 @@ "deleteAccount": { "message": "Удалить аккаунт" }, + "dismiss": { + "message": "Отклюнить" + }, "downloadGoogleChrome": { "message": "Скачать Google Chrome" }, diff --git a/app/_locales/sk/messages.json b/app/_locales/sk/messages.json index d975048d8..697c58936 100644 --- a/app/_locales/sk/messages.json +++ b/app/_locales/sk/messages.json @@ -1,21 +1,33 @@ { + "privacyModeDefault": { + "message": "Režim súkromia je povolený. Je prednastavený automaticky" + }, "chartOnlyAvailableEth": { "message": "Graf je k dispozícii iba v sieťach Ethereum." }, + "confirmClear": { + "message": "Naozaj chcete vymazať schválené webové stránky?" + }, "contractInteraction": { "message": "Zmluvná interakcia" }, + "clearApprovalData": { + "message": "Jasné údaje o schválení" + }, "reject": { "message": "Odmítnout" }, - "likeToConnect": { + "providerRequest": { "message": "$1 sa chce pripojiť k vášmu účtu" }, + "providerRequestInfo": { + "message": "Níže uvedená doména se pokouší požádat o přístup k API Ethereum, aby mohla komunikovat s blokádou Ethereum. Před schválením přístupu Ethereum vždy zkontrolujte, zda jste na správném místě." + }, "about": { "message": "Informácie" }, "aboutSettingsDescription": { - "message": "Verzia, centrum podpory a kontaktné informácie" + "message": "Verzia, centrum podpory a kontaktné informácie." }, "acceleratingATransaction": { "message": "*Urýchlenie transakcie pomocou vyššej ceny za GAS zvyšuje šance na rýchlejšie spracovanie v sieti, nie je to však vždy zaručené." @@ -51,7 +63,7 @@ "message": "Rozšírené" }, "advancedSettingsDescription": { - "message": "Získajte prístup k vývojárskym funkciám, sťahujte si Stavové denníky, resetujte účet, nastavujte testovacie siete a vlastné RPC" + "message": "Získajte prístup k vývojárskym funkciám, sťahujte si Stavové denníky, resetujte účet, nastavujte testovacie siete a vlastné RPC." }, "advancedOptions": { "message": "Rozšírené nastavenia" @@ -142,6 +154,10 @@ "basic": { "message": "Základné" }, + "betweenMinAndMax": { + "message": "musí být větší nebo roven $1 a menší nebo roven $2.", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerView": { "message": "Zobraziť účet na $1", "description": "$1 replaced by URL for custom block explorer" @@ -227,6 +243,9 @@ "connect": { "message": "Pripojenie" }, + "connectRequest": { + "message": "Požiadavka na pripojenie" + }, "connectingTo": { "message": "Pripája sa k $1" }, @@ -266,6 +285,9 @@ "copiedExclamation": { "message": "Zkopírováno!" }, + "copy": { + "message": "Kopírovat" + }, "copyAddress": { "message": "Kopírovať adresu do schránky" }, @@ -344,6 +366,9 @@ "directDepositEtherExplainer": { "message": "Pokud už vlastníte nějaký Ether, nejrychleji ho dostanete do peněženky přímým vkladem." }, + "dismiss": { + "message": "Zatvoriť" + }, "done": { "message": "Hotovo" }, @@ -509,6 +534,10 @@ "getStarted": { "message": "Začať" }, + "greaterThanMin": { + "message": "musí být větší nebo roven $1.", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "Sme radi, že vás vidíme." }, @@ -614,6 +643,10 @@ "ledgerAccountRestriction": { "message": "Skôr ako budete môcť pridať nový účet, musíte použiť svoj posledný účet." }, + "lessThanMax": { + "message": "musí být menší nebo roven $1.", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "Áno, poďme to nastaviť!" }, @@ -803,6 +836,9 @@ "message": "Vložte zde svůj privátní klíč:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Svou klíčovou frázi vložte zde!" + }, "pending": { "message": "prebieha" }, @@ -1110,6 +1146,9 @@ "storePhrase": { "message": "Túto frázu uložte do správcu hesiel ako 1Password." }, + "submit": { + "message": "Odeslat" + }, "submitted": { "message": "Odesláno" }, @@ -1271,6 +1310,9 @@ "userName": { "message": "Meno používateľa" }, + "validFileImport": { + "message": "Musíte vybrat validní soubor k importu." + }, "viewAccount": { "message": "Zobrazit účet" }, diff --git a/app/_locales/sl/messages.json b/app/_locales/sl/messages.json index 312bcecfb..643e62c23 100644 --- a/app/_locales/sl/messages.json +++ b/app/_locales/sl/messages.json @@ -1,21 +1,33 @@ { + "privacyModeDefault": { + "message": "Zasebnostni način je zdaj privzeto omogočen" + }, "chartOnlyAvailableEth": { "message": "Grafikon na voljo le v glavnih omrežjih." }, + "confirmClear": { + "message": "Ste prepričani da želite počistiti odobrene spletne strani?" + }, "contractInteraction": { "message": "Interakcija s pogodbo" }, + "clearApprovalData": { + "message": "Počisti podatke o odobritvi" + }, "reject": { "message": "Zavrni" }, - "likeToConnect": { + "providerRequest": { "message": "$1 se želi povezati z vašim računom." }, + "providerRequestInfo": { + "message": "Domena zahteva dostop do verige blokov in ogled vašega računa. Pred potrditvjo vedno preverite ali ste na želeni spletni strani." + }, "about": { "message": "O možnostih" }, "aboutSettingsDescription": { - "message": "Različica, center za podporo in podatki za stik" + "message": "Različica, center za podporo in podatki za stik." }, "acceleratingATransaction": { "message": "* Pospešitev transakcije z višjo gas ceno poveča njene možnosti za hitrejšo obdelavo v omrežju, vendar ni vedno zagotovljena." @@ -51,7 +63,7 @@ "message": "Napredno" }, "advancedSettingsDescription": { - "message": "Dostopite do funkcij razvijalca, prenesite dnevnike držav, ponastavite račun, nastavite testne mreže in RPC po meri" + "message": "Dostopite do funkcij razvijalca, prenesite dnevnike držav, ponastavite račun, nastavite testne mreže in RPC po meri." }, "advancedOptions": { "message": "Napredne možnosti" @@ -145,6 +157,10 @@ "basic": { "message": "Osnovno" }, + "betweenMinAndMax": { + "message": "mora biti večji ali enak $1 in manjši ali enak $1.", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerUrl": { "message": "Blokiraj Explorer" }, @@ -236,6 +252,9 @@ "connect": { "message": "Poveži" }, + "connectRequest": { + "message": "Zahteva za povezavo" + }, "connectingTo": { "message": "Povezovanje na $1" }, @@ -275,6 +294,9 @@ "copiedExclamation": { "message": "Kopirano!" }, + "copy": { + "message": "Kopiraj" + }, "copyAddress": { "message": "Kopiraj naslov v odložišče" }, @@ -353,6 +375,9 @@ "directDepositEtherExplainer": { "message": "Če že imate Ether, ga lahko najhitreje dobite v MetaMask z neposrednim vplačilom." }, + "dismiss": { + "message": "Opusti" + }, "done": { "message": "Končano" }, @@ -512,6 +537,10 @@ "getStarted": { "message": "Začnite" }, + "greaterThanMin": { + "message": "mora biti večji ali enak $1.", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "Veseli nas, da ste nas spet obiskali." }, @@ -624,6 +653,10 @@ "ledgerAccountRestriction": { "message": "Za dodajanje novega računa morate uporabiti zadnji račun." }, + "lessThanMax": { + "message": "mora biti manjši ali enak $1.", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "Lotimo se nastavitev!" }, @@ -822,6 +855,9 @@ "message": "Tukaj prilepite vaš zasebni ključ:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Tukaj prilepite seed phase!" + }, "pending": { "message": "v obdelavi" }, @@ -1132,6 +1168,9 @@ "storePhrase": { "message": "To geslo shranite v upravitelja gesel, kot je 1Password." }, + "submit": { + "message": "Potrdi" + }, "submitted": { "message": "Potrjeno" }, @@ -1299,6 +1338,9 @@ "userName": { "message": "Uporabniško ime" }, + "validFileImport": { + "message": "Za uvoz morate izbrati pravilno datoteko." + }, "viewAccount": { "message": "Poglej račun" }, diff --git a/app/_locales/sr/messages.json b/app/_locales/sr/messages.json index f4084996f..1cd653ed7 100644 --- a/app/_locales/sr/messages.json +++ b/app/_locales/sr/messages.json @@ -1,21 +1,33 @@ { + "privacyModeDefault": { + "message": "Režim privatnosti je podrazumevano omogućen" + }, "chartOnlyAvailableEth": { "message": "Grafikon dostupan jedino na mrežama Ethereum." }, + "confirmClear": { + "message": "Da li ste sigurni da želite da obrišete odobrene veb lokacije?" + }, "contractInteraction": { "message": "Ugovorna interakcija" }, + "clearApprovalData": { + "message": "Obrišite privatne podatke" + }, "reject": { "message": "Одбиј" }, - "likeToConnect": { + "providerRequest": { "message": "$1 bi hteo da se poveže sa vašim nalogom" }, + "providerRequestInfo": { + "message": "Ovaj sajt traži pristup kako bi video vašu trenutnu adresu naloga. Uvek budite sigurni da verujete sajtovima s kojima komunicirate." + }, "about": { "message": "Основни подаци" }, "aboutSettingsDescription": { - "message": "Verzija, centar za podršku i podaci za kontakt" + "message": "Verzija, centar za podršku i podaci za kontakt." }, "acceleratingATransaction": { "message": "* Time što se ubrzava transakcija koristeći veću gas cenu, povećavaju se šanse da se procesuira brže od strane mreže, ali to nije uvek zagarantovano." @@ -51,7 +63,7 @@ "message": "Напредне опције" }, "advancedSettingsDescription": { - "message": "Pristupite funkcijama za programere, preuzmite državne evidencije, resetujte nalog, postavite testne mreže i prilagođeni RPC" + "message": "Pristupite funkcijama za programere, preuzmite državne evidencije, resetujte nalog, postavite testne mreže i prilagođeni RPC." }, "advancedOptions": { "message": "Dodatne opcije" @@ -145,6 +157,10 @@ "basic": { "message": "Основна" }, + "betweenMinAndMax": { + "message": "mora biti veće od ili jednako $1 i manje od ili jednako $2", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerUrl": { "message": "Blok istraživač" }, @@ -233,6 +249,9 @@ "connect": { "message": "Повезивање" }, + "connectRequest": { + "message": "Zahtev za povezivanjem" + }, "connectingTo": { "message": "Povezuje se na $1" }, @@ -272,6 +291,9 @@ "copiedExclamation": { "message": "Kopirano!" }, + "copy": { + "message": "Копирај" + }, "copyAddress": { "message": "Kopirajte adresu u ostavu" }, @@ -350,6 +372,9 @@ "directDepositEtherExplainer": { "message": "Ako već imate neki Ether, najbrži način da preuzmete Ether u svoj novi novčanik jeste direktnim deponovanjem." }, + "dismiss": { + "message": "Одбаци" + }, "done": { "message": "Gotovo" }, @@ -515,6 +540,10 @@ "getStarted": { "message": "Започнимо" }, + "greaterThanMin": { + "message": "mora biti veći od ili jednak $1.", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "Drago nam je da vas vidimo." }, @@ -633,6 +662,10 @@ "ledgerAccountRestriction": { "message": "Treba da koristite svoj poslednji nalog pre nego što budete mogli da dodate novi." }, + "lessThanMax": { + "message": "mora biti manje od ili jednako $1.", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "Da, hajde da sve podesimo!" }, @@ -828,6 +861,9 @@ "message": "Ovde nalepite vaš niz privatnog ključa", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Nalepite ovde svoju početnu frazu!" + }, "pending": { "message": "u toku" }, @@ -1141,6 +1177,9 @@ "storePhrase": { "message": "Čuvajte ovaj izraz u menadžeru šifri kao što je 1Password." }, + "submit": { + "message": "Пошаљи" + }, "submitted": { "message": "Prosleđeno" }, @@ -1302,6 +1341,9 @@ "userName": { "message": "Корисничко име" }, + "validFileImport": { + "message": "Morate odabrati važeću datoteku da biste ih uvezli." + }, "viewAccount": { "message": "Прикажи налог" }, diff --git a/app/_locales/sv/messages.json b/app/_locales/sv/messages.json index 750357045..6d89b6e5c 100644 --- a/app/_locales/sv/messages.json +++ b/app/_locales/sv/messages.json @@ -1,10 +1,19 @@ { + "privacyModeDefault": { + "message": "Integritetsläge är nu aktiverat som standard" + }, "chartOnlyAvailableEth": { "message": "Tabellen är endast tillgänglig på Ethereum-nätverk." }, + "confirmClear": { + "message": "Är du säker på att du vill rensa godkända webbplatser?" + }, "contractInteraction": { "message": "Kontraktinteraktion" }, + "clearApprovalData": { + "message": "Rensa personlig data" + }, "appName": { "message": "MetaMask", "description": "The name of the application" @@ -12,14 +21,17 @@ "reject": { "message": "Avvisa" }, - "likeToConnect": { + "providerRequest": { "message": "$1 vill ansluta till ditt konto" }, + "providerRequestInfo": { + "message": "Den här sidan begär åtkomst till din aktuella kontoadress. Se till att du kan lita på de sidor du använder dig av." + }, "about": { "message": "Om" }, "aboutSettingsDescription": { - "message": "Version, supportcenter och kontaktinformation" + "message": "Version, supportcenter och kontaktinformation." }, "acceleratingATransaction": { "message": "* Att snabba upp en överföring genom att använda ett högre gaspris ökar chanserna för att överföringen ska hanteras snabbare av nätverket, men det är inte en garanti." @@ -55,7 +67,7 @@ "message": "Avancerat" }, "advancedSettingsDescription": { - "message": "Åtkomst till verktyg för utvecklare, ladda ner loggar, återställ konto, upprätta testnätverk och skräddarsy RPC" + "message": "Åtkomst till verktyg för utvecklare, ladda ner loggar, återställ konto, upprätta testnätverk och skräddarsy RPC." }, "advancedOptions": { "message": "Avancerade alternativ" @@ -145,6 +157,10 @@ "basic": { "message": "Grunder" }, + "betweenMinAndMax": { + "message": "måste vara större än/lika med $1, eller mindre än/lika med $2.", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerUrl": { "message": "Blockera Utforskaren" }, @@ -230,6 +246,9 @@ "connect": { "message": "Ansluta" }, + "connectRequest": { + "message": "Anslutningsförfrågan" + }, "connectingTo": { "message": "Ansluter till $1" }, @@ -269,6 +288,9 @@ "copiedExclamation": { "message": "Kopierades!" }, + "copy": { + "message": "Kopiera" + }, "copyAddress": { "message": "Kopiera adress till urklipp" }, @@ -347,6 +369,9 @@ "directDepositEtherExplainer": { "message": "Om du redan har Ether är det snabbaste sättet att få Ether i din nya plånbok att göra en direktinsättning." }, + "dismiss": { + "message": "Ta bort permanent" + }, "done": { "message": "Klart" }, @@ -508,6 +533,10 @@ "getStarted": { "message": "Komma igång" }, + "greaterThanMin": { + "message": "måste vara större än eller lika med $1.", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "Vi är glada att se dig." }, @@ -626,6 +655,10 @@ "ledgerAccountRestriction": { "message": "Du måste använda ditt senaste konto innan du kan lägga till ett nytt." }, + "lessThanMax": { + "message": "måste vara mindre än eller lika med $1.", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "Ja, kör igång!" }, @@ -821,6 +854,9 @@ "message": "Klistra in din privata nyckel här:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Klistra in din nyckelfras här!" + }, "pending": { "message": "väntar" }, @@ -1134,6 +1170,9 @@ "storePhrase": { "message": "Lagra denna fras i en lösenordshanterare såsom 1Password." }, + "submit": { + "message": "Skicka" + }, "submitted": { "message": "Inskickat" }, @@ -1292,6 +1331,9 @@ "userName": { "message": "Användarnamn" }, + "validFileImport": { + "message": "Du måste välja en giltig fil för att importera." + }, "viewAccount": { "message": "Visa konto" }, diff --git a/app/_locales/sw/messages.json b/app/_locales/sw/messages.json index 38c89aca8..76d1897ac 100644 --- a/app/_locales/sw/messages.json +++ b/app/_locales/sw/messages.json @@ -1,10 +1,19 @@ { + "privacyModeDefault": { + "message": "Hali ya Faragha sasa imewezeshwa kwa chaguomsingi" + }, "chartOnlyAvailableEth": { "message": "Zogoa inapatikana kwenye mitandao ya Ethereum pekee." }, + "confirmClear": { + "message": "Una uhakika unataka kufuta tovuti zilizodihinishwa?" + }, "contractInteraction": { "message": "Mwingiliono wa Mkataba" }, + "clearApprovalData": { + "message": "Futa Data za Faragha" + }, "appName": { "message": "MetaMask", "description": "The name of the application" @@ -12,14 +21,17 @@ "reject": { "message": "Kataa" }, - "likeToConnect": { + "providerRequest": { "message": "$1 ingependa kuunganishwa kwenye akaunti yako" }, + "providerRequestInfo": { + "message": "Tovuti hii inaomba idhini ya kuangalia anwani yako ya akaunti ya sasa. Daima hakikisha unaziamami tovuti ambazo unaingiliana nazo." + }, "about": { "message": "Kuhusu" }, "aboutSettingsDescription": { - "message": "Toleo, kituo cha msaada, na taarifa za mawasiliano" + "message": "Toleo, kituo cha msaada, na taarifa za mawasiliano." }, "acceleratingATransaction": { "message": "*Kuwezesha muamala kwa kutumia bei ya juu ya gesi huongeza uwezekano wake wa kushughulikiwa na mtandao haraka, lakini hauhakikishiwi siku zote." @@ -55,7 +67,7 @@ "message": "Mipangilio ya kina" }, "advancedSettingsDescription": { - "message": "Vipengele vya idhini ya msanidi, Kumbukumbu za Hali ya kupakua, Kufuta Akaunti, mitando ya majaribio ya kuweka mipangilio na RPC maalumu" + "message": "Vipengele vya idhini ya msanidi, Kumbukumbu za Hali ya kupakua, Kufuta Akaunti, mitando ya majaribio ya kuweka mipangilio na RPC maalumu." }, "advancedOptions": { "message": "Machaguo ya Juu" @@ -145,6 +157,10 @@ "basic": { "message": "Msingi" }, + "betweenMinAndMax": { + "message": "inapaswa kuwa kubwa kuliko au sawa na $1 na ndogo kuliko au sawa na $2.", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerView": { "message": "Tazama akaunti kwenye $1", "description": "$1 replaced by URL for custom block explorer" @@ -230,6 +246,9 @@ "connect": { "message": "Unganisha" }, + "connectRequest": { + "message": "Unganisha Ombi" + }, "connectingTo": { "message": "Inaunganisha kwenye $1" }, @@ -269,6 +288,9 @@ "copiedExclamation": { "message": "Imenakiliwa!" }, + "copy": { + "message": "Nakili" + }, "copyAddress": { "message": "Nakili anwani kwenye ubao wa kunakilia" }, @@ -347,6 +369,9 @@ "directDepositEtherExplainer": { "message": "Ikiwa tayari una sarafu kadhaa za Ether, njia rahisi ya kupata Ether kwenye waleti yako mpya kupitia kuweka moja kwa moja." }, + "dismiss": { + "message": "Ondoa" + }, "done": { "message": "Imekamilika" }, @@ -508,6 +533,10 @@ "getStarted": { "message": "Anza" }, + "greaterThanMin": { + "message": "inapaswa iwe kubwa kuliko au sawa na $1.", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "Tuna furaha kukuona" }, @@ -620,6 +649,10 @@ "ledgerAccountRestriction": { "message": "Unapaswa kutumia akaunti yako ya mwisho kabla hujaongeza mpya." }, + "lessThanMax": { + "message": "inapaswa kuwa ndogo kuliko au sawa na $1. ", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "Ndiyo, hebu tuweke mipangilio!" }, @@ -815,6 +848,9 @@ "message": "Bandika uzi wako wa ufunguo binafsi hapa:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Bandika kirai kianzio chako hapa!" + }, "pending": { "message": "inasubiri" }, @@ -1128,6 +1164,9 @@ "storePhrase": { "message": "Hifadhi kirai hiki kwenye kidhibiti nenosiri kama vile 1Password." }, + "submit": { + "message": "Wasilisha" + }, "submitted": { "message": "Imewasilishwa" }, @@ -1295,6 +1334,9 @@ "userName": { "message": "Jina la mtumiaji" }, + "validFileImport": { + "message": "Ni lazima uchague faili halali ili uhamishe." + }, "viewAccount": { "message": "Angalia Akaunti" }, diff --git a/app/_locales/ta/messages.json b/app/_locales/ta/messages.json index dc48abb02..b5be67520 100644 --- a/app/_locales/ta/messages.json +++ b/app/_locales/ta/messages.json @@ -1,10 +1,19 @@ { + "confirmClear": { + "message": "அங்கீகரிக்கப்பட்ட வலைத்தளங்களை நிச்சயமாக நீக்க விரும்புகிறீர்களா?" + }, + "clearApprovalData": { + "message": "ஒப்புதல் தரவை அழி" + }, "approve": { "message": "ஒப்புதல்" }, "reject": { "message": "நிராகரி" }, + "providerRequestInfo": { + "message": "கீழே பட்டியலிடப்பட்டுள்ள டொமைன் Web3 ஏபிஐ அணுகலைக் கோருவதற்கு முயற்சிக்கிறது, எனவே இது Ethereum blockchain உடன் தொடர்பு கொள்ள முடியும். Web3 அணுகுமுறையை அங்கீகரிப்பதற்கு முன் சரியான தளத்தில் இருப்பதை எப்போதும் இருமுறை சரிபார்க்கவும்." + }, "account": { "message": "கணக்கு" }, @@ -49,6 +58,10 @@ "balanceIsInsufficientGas": { "message": "நடப்பு வாயு மொத்தம் போதுமான சமநிலை" }, + "betweenMinAndMax": { + "message": "$ 1 க்கும் அதிகமாகவும் அல்லது $ 2 க்கு சமமாகவும் இருக்க வேண்டும்.", + "description": "ஹெக்ஸ் உள்ளீடு தசம உள்ளீடு என உதவி" + }, "blockiesIdenticon": { "message": "ப்ளாக்கிஸ் ஐடென்டிகோன் பயன்பாட்டு" }, @@ -91,6 +104,9 @@ "copiedExclamation": { "message": "நகலெடுக்கப்பட்டன!" }, + "copy": { + "message": "நகலெடு" + }, "copyToClipboard": { "message": "கிளிப்போர்டுக்கு நகலெடு" }, @@ -208,6 +224,10 @@ "message": "$ 1 க்கு ஒரு குழாய் இருந்து ஈதர் கிடைக்கும்$1", "description": "ஈத்தர் குழாய் ஐந்து பிணைய பெயர் காட்டுகிறது" }, + "greaterThanMin": { + "message": "$ 1 க்கும் அதிகமாகவோ அல்லது சமமாகவோ இருக்க வேண்டும்", + "description": "ஹெக்ஸ் உள்ளீடு தசம உள்ளீடு என உதவி" + }, "here": { "message": "இங்கே", "description": "இங்கே-கிளிக் செய்யவும்- மேலும் தகவலுக்கு (troubleTokenBalances செல்கிறது)" @@ -272,6 +292,10 @@ "learnMore": { "message": "மேலும் அறிக" }, + "lessThanMax": { + "message": "$ 1 க்கும் குறைவாகவோ அல்லது சமமாகவோ இருக்க வேண்டும்.", + "description": "ஹெக்ஸ் உள்ளீடு தசம உள்ளீடு என உதவி" + }, "likeToAddTokens": { "message": "இந்த டோக்கன்களைச் சேர்க்க விரும்புகிறீர்களா?" }, @@ -350,6 +374,9 @@ "message": "இங்கே உங்கள் தனிப்பட்ட விசை சரத்தை ஒட்டுக:", "description": "ஒரு தனிப்பட்ட விசை ஒரு கணக்கை இறக்குமதி செய்ய" }, + "pasteSeed": { + "message": "இங்கே உங்கள் விதை சொற்றொடரை ஒட்டவும்!" + }, "personalAddressDetected": { "message": "தனிப்பட்ட முகவரி கண்டறியப்பட்டது. டோக்கன் ஒப்பந்த முகவரியை உள்ளிடவும்." }, @@ -474,6 +501,9 @@ "stateLogError": { "message": "மாநில பதிவுகளை மீட்டெடுப்பதில் பிழை." }, + "submit": { + "message": "சமர்ப்பி" + }, "submitted": { "message": "சமர்ப்பிக்கப்பட்டது" }, @@ -530,6 +560,9 @@ "usedByClients": { "message": "பல்வேறு வாடிக்கையாளர்கள் பல்வேறு பயன்படுத்திய" }, + "validFileImport": { + "message": "இறக்குமதி செய்ய சரியான கோப்பு தேர்ந்தெடுக்க வேண்டும்." + }, "viewAccount": { "message": "கணக்கைப் பார்" }, @@ -569,6 +602,9 @@ "delete": { "message": "நீக்கு" }, + "dismiss": { + "message": "நிராகரி" + }, "fast": { "message": "வேகமான" }, diff --git a/app/_locales/te/messages.json b/app/_locales/te/messages.json index 2df11217d..ba588ca46 100644 --- a/app/_locales/te/messages.json +++ b/app/_locales/te/messages.json @@ -42,6 +42,9 @@ "connect": { "message": "కనెక్ట్ చేయండి" }, + "copy": { + "message": "కాపీ చెయ్యి" + }, "copyToClipboard": { "message": "క్లిప్‌బోర్డ్‌కు కాపీ చేయి" }, @@ -54,6 +57,9 @@ "details": { "message": "వివరాలు" }, + "dismiss": { + "message": "తొలగించు" + }, "done": { "message": "పూర్తయింది" }, @@ -125,6 +131,9 @@ "settings": { "message": "సెట్టింగ్‌లు" }, + "submit": { + "message": "సమర్పించు" + }, "tryAgain": { "message": "మళ్లీ ప్రయత్నించు" }, diff --git a/app/_locales/th/messages.json b/app/_locales/th/messages.json index 6b3686f45..f5fcf3edf 100644 --- a/app/_locales/th/messages.json +++ b/app/_locales/th/messages.json @@ -1,10 +1,19 @@ { + "confirmClear": { + "message": "คุณแน่ใจหรือไม่ว่าต้องการล้างเว็บไซต์ที่ผ่านการอนุมัติ" + }, + "clearApprovalData": { + "message": "ล้างข้อมูลการอนุมัติ" + }, "reject": { "message": "ปฏิเสธ" }, - "likeToConnect": { + "providerRequest": { "message": "$1 ต้องการเชื่อมต่อกับบัญชีของคุณ" }, + "providerRequestInfo": { + "message": "โดเมนที่แสดงด้านล่างกำลังพยายามขอเข้าถึง API ของ Ethereum เพื่อให้สามารถโต้ตอบกับบล็อค Ethereum ได้ ตรวจสอบว่าคุณอยู่ในไซต์ที่ถูกต้องก่อนที่จะอนุมัติการเข้าถึง Ethereum เสมอ" + }, "about": { "message": "เกี่ยวกับ" }, @@ -55,6 +64,10 @@ "balanceIsInsufficientGas": { "message": "ยอดคงเหลือไม่พอสำหรับจ่ายค่าแก๊สทั้งหมด" }, + "betweenMinAndMax": { + "message": "ต้องมากกว่าหรือเท่ากับ $1 และน้อยกว่าหรือเท่ากับ $2", + "description": "helper for inputting hex as decimal input" + }, "blockiesIdenticon": { "message": "ใช้งาน Blockies Identicon" }, @@ -118,6 +131,9 @@ "copiedExclamation": { "message": "คัดลอกแล้ว!" }, + "copy": { + "message": "คัดลอก" + }, "copyToClipboard": { "message": "คัดลอกไปคลิปบอร์ด" }, @@ -175,6 +191,9 @@ "directDepositEtherExplainer": { "message": "ถ้าคุณมีอีเธอร์อยู่แล้ววิธีการที่เร็วที่สุดในการเอาเงินเข้ากระเป๋าใหม่ก็คือการโอนตรงๆ" }, + "dismiss": { + "message": "ปิด" + }, "done": { "message": "เสร็จสิ้น" }, @@ -265,6 +284,10 @@ "message": "รับอีเธอร์ที่ปล่อยจาก $1", "description": "Displays network name for Ether faucet" }, + "greaterThanMin": { + "message": "ต้องมากกว่าหรือเท่ากับ $1.", + "description": "helper for inputting hex as decimal input" + }, "here": { "message": "ที่นี่", "description": "as in -click here- for more information (goes with troubleTokenBalances)" @@ -329,6 +352,10 @@ "learnMore": { "message": "เรียนรู้เพิ่มเติม" }, + "lessThanMax": { + "message": "ต้องน้อยกว่าหรือเท่ากับ $1.", + "description": "helper for inputting hex as decimal input" + }, "likeToAddTokens": { "message": "คุณต้องการเพิ่มโทเค็นเหล่านี้หรือไม่?" }, @@ -416,6 +443,9 @@ "message": "วางคีย์ส่วนตัวของคุณที่นี่:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "วางคำชีดของคุณที่นี่!" + }, "personalAddressDetected": { "message": "ตรวจพบแอดแดรสส่วนตัวแล้ว ใส่แอดแดรสสัญญาโทเค็น" }, @@ -558,6 +588,9 @@ "storePhrase": { "message": "เก็บ Phrase นี้ในตัวจัดการรหัสผ่าน เช่น 1Password" }, + "submit": { + "message": "ตกลง" + }, "supportCenter": { "message": "ไปที่ศูนย์สนับสนุนของเรา" }, @@ -629,6 +662,9 @@ "usedByClients": { "message": "ถูกใช้งานโดยหลายไคลเอนท์" }, + "validFileImport": { + "message": "คุณต้องเลือกไฟล์ที่ถูกต้องเพื่อนำเข้า" + }, "viewAccount": { "message": "ดูบัญชี" }, diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json index fdb1ae927..fe2332f2f 100644 --- a/app/_locales/tr/messages.json +++ b/app/_locales/tr/messages.json @@ -1,7 +1,16 @@ { + "confirmClear": { + "message": "Onaylanmış web sitelerini silmek istediğinizden emin misiniz?" + }, + "clearApprovalData": { + "message": "Onay verilerini temizle" + }, "reject": { "message": "Reddetmek" }, + "providerRequestInfo": { + "message": "Aşağıda listelenen etki alanı, Ethereum API'sine erişim talep etmeye çalışmaktadır, böylece Ethereum blockchain ile etkileşime girebilir. Web3 erişimini onaylamadan önce her zaman doğru sitede olduğunuzu kontrol edin." + }, "account": { "message": "Hesap" }, @@ -49,6 +58,10 @@ "balanceIsInsufficientGas": { "message": "Toplam gas için yetersiz bakiye" }, + "betweenMinAndMax": { + "message": "$1'e eşit veya daha büyük olmalı ve $2'den küçük veya eşit olmalı", + "description": "helper for inputting hex as decimal input" + }, "blockiesIdenticon": { "message": "Blockies Identicon kullan" }, @@ -103,6 +116,9 @@ "copiedExclamation": { "message": "Kopyalandı!" }, + "copy": { + "message": "Kopyala" + }, "copyToClipboard": { "message": "Panoya kopyala" }, @@ -278,6 +294,10 @@ "learnMore": { "message": "Daha fazla bilgi." }, + "lessThanMax": { + "message": "$1'den az veya eşit olmalıdır.", + "description": "helper for inputting hex as decimal input" + }, "likeToAddTokens": { "message": "Bu jetonlara adres eklemek ister misiniz?" }, @@ -359,6 +379,9 @@ "message": "Özel anahtar dizinizi buraya yapıştırın:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Kaynak ifadenizi buraya yapıştırın!" + }, "personalAddressDetected": { "message": "Kişisel adres tespit edilidi. Jeton sözleşme adresini girin." }, @@ -471,6 +494,9 @@ "stateLogError": { "message": "Durum kayıtlarını alma hatası" }, + "submit": { + "message": "Gönder" + }, "submitted": { "message": "Gönderildi" }, @@ -527,6 +553,9 @@ "usedByClients": { "message": "Farklı istemciler tarafından kullanılmakta" }, + "validFileImport": { + "message": "Almak için geçerli bir dosya seçmelisiniz" + }, "viewAccount": { "message": "Hesabı İncele" }, diff --git a/app/_locales/uk/messages.json b/app/_locales/uk/messages.json index cec516b6c..d269044b3 100644 --- a/app/_locales/uk/messages.json +++ b/app/_locales/uk/messages.json @@ -1,21 +1,33 @@ { + "privacyModeDefault": { + "message": "Режим конфіденційності тепер увімкнено за замовчуванням" + }, "chartOnlyAvailableEth": { "message": "Таблиця доступна тільки в мережах Ethereum." }, + "confirmClear": { + "message": "Ви впевнені, що хочете очистити затверджені веб-сайти?" + }, "contractInteraction": { "message": "Контрактна взаємодія" }, + "clearApprovalData": { + "message": "Очистити приватні дані" + }, "reject": { "message": "Відхилити" }, - "likeToConnect": { + "providerRequest": { "message": "$1 бажає підключитися до вашого облікового запису" }, + "providerRequestInfo": { + "message": "Цей сайт запитує доступ на перегляд вашої поточної адреси облікового запису. Завжди взаємодійте лише з веб-сайтами, яким довіряєте." + }, "about": { "message": "Про Google Chrome" }, "aboutSettingsDescription": { - "message": "Версія, центр підтримки та контактна інформація" + "message": "Версія, центр підтримки та контактна інформація." }, "acceleratingATransaction": { "message": "* Прискорення транзакції за допомогою вищих цін на газ підвищує її шанси бути обробленою мережею швидше, але це не завжди гарантовано." @@ -51,7 +63,7 @@ "message": "Розширені" }, "advancedSettingsDescription": { - "message": "Отримайте доступ до функцій розробника, завантажте Логи станів, перезапустіть обліковий запис, налаштуйте тестові сітки та персоніфіковані RPC" + "message": "Отримайте доступ до функцій розробника, завантажте Логи станів, перезапустіть обліковий запис, налаштуйте тестові сітки та персоніфіковані RPC." }, "advancedOptions": { "message": "Додаткові параметри" @@ -145,6 +157,10 @@ "basic": { "message": "Основні параметри" }, + "betweenMinAndMax": { + "message": "має бути більшим або дорівнювати $1 і меншим або дорівнювати $2.", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerUrl": { "message": "Блокувати Explorer" }, @@ -236,6 +252,9 @@ "connect": { "message": "Під’єднатися" }, + "connectRequest": { + "message": "Запит на з'єднання" + }, "connectingTo": { "message": "Під'єднуємось до $1" }, @@ -275,6 +294,9 @@ "copiedExclamation": { "message": "Скопійовано!" }, + "copy": { + "message": "Копіювати" + }, "copyAddress": { "message": "Копіювати адресу в буфер обміну" }, @@ -353,6 +375,9 @@ "directDepositEtherExplainer": { "message": "Якщо ви вже маєте ефір, пряме переведення – найшвидший спосіб передати ефір у свій гаманець." }, + "dismiss": { + "message": "Відхилити" + }, "done": { "message": "Готово" }, @@ -518,6 +543,10 @@ "getStarted": { "message": "Почати" }, + "greaterThanMin": { + "message": "має бути більшим або рівним $1.", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "Раді вас бачити." }, @@ -636,6 +665,10 @@ "ledgerAccountRestriction": { "message": "Потрібно скористатися своїм останнім обліковим записом, перш ніж додавати новий." }, + "lessThanMax": { + "message": "має бути більшим або дорівнювати $1.", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "Так, давайте налаштуємо!" }, @@ -837,6 +870,9 @@ "message": "Вставте ваш рядок з особистим ключем сюди:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Вставте сюди свою мнемонічну фразу!" + }, "pending": { "message": "очікує" }, @@ -1150,6 +1186,9 @@ "storePhrase": { "message": "Зберігайте цю фразу у менеджері паролів, як 1Password." }, + "submit": { + "message": "Надіслати" + }, "submitted": { "message": "Надіслано" }, @@ -1317,6 +1356,9 @@ "userName": { "message": "Ім’я користувача" }, + "validFileImport": { + "message": "Потрібно вибрати дійсний файл для імпорту." + }, "viewAccount": { "message": "Переглянути обліковий запис" }, diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json index 6136403ba..b22361678 100644 --- a/app/_locales/vi/messages.json +++ b/app/_locales/vi/messages.json @@ -1,7 +1,16 @@ { + "confirmClear": { + "message": "Bạn có chắc chắn muốn xóa các trang web được phê duyệt không?" + }, + "clearApprovalData": { + "message": "Xóa dữ liệu phê duyệt" + }, "reject": { "message": "Từ chối" }, + "providerRequestInfo": { + "message": "Miền được liệt kê bên dưới đang cố gắng yêu cầu quyền truy cập vào API Ethereum để nó có thể tương tác với chuỗi khối Ethereum. Luôn kiểm tra kỹ xem bạn có đang ở đúng trang web trước khi phê duyệt quyền truy cập Ethereum hay không." + }, "account": { "message": "Tài khoản" }, @@ -40,6 +49,10 @@ "balanceIsInsufficientGas": { "message": "Số dư không đủ để thanh toán tổng tiền gas hiện tại" }, + "betweenMinAndMax": { + "message": "phải nhiều hơn hoặc bằng $1 và ít hơn hoặc bằng $2.", + "description": "helper for inputting hex as decimal input" + }, "buyCoinSwitch": { "message": "Mua trên CoinSwitch" }, @@ -70,6 +83,9 @@ "copiedExclamation": { "message": "Đã sao chép!" }, + "copy": { + "message": "Sao chép" + }, "copyToClipboard": { "message": "Đã sao chép vào clipboard" }, @@ -163,6 +179,10 @@ "message": "Lấy Ether từ vòi với giá $1", "description": "Displays network name for Ether faucet" }, + "greaterThanMin": { + "message": "phải nhiều hơn hoặc bằng $1", + "description": "helper for inputting hex as decimal input" + }, "here": { "message": "tại đây", "description": "as in -click here- for more information (goes with troubleTokenBalances)" @@ -203,6 +223,10 @@ "kovan": { "message": "Mạng thử nghiệm Kovan" }, + "lessThanMax": { + "message": "phải ít hơn hoặc bằng $1.", + "description": "helper for inputting hex as decimal input" + }, "loading": { "message": "Đang tải..." }, @@ -260,6 +284,9 @@ "message": "Dán dãy khóa cá nhân của bạn tại đây:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "Dán Mật Khẩu Sinh Khoá (seed phrase) của bạn tại đây!" + }, "privateKey": { "message": "Khóa Bí Mật", "description": "select this type of file to use to import an account" @@ -321,6 +348,9 @@ "sigRequest": { "message": "Yêu cầu chữ ký" }, + "submit": { + "message": "Gửi đi" + }, "testFaucet": { "message": "Vòi nhận tiền ETH ảo để thử nghiệm" }, diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json index e92813a9a..90c7642dc 100644 --- a/app/_locales/zh_CN/messages.json +++ b/app/_locales/zh_CN/messages.json @@ -1,16 +1,28 @@ { + "privacyModeDefault": { + "message": "现已默认启用隐私模式" + }, "chartOnlyAvailableEth": { "message": "聊天功能仅对以太坊网络开放。" }, + "confirmClear": { + "message": "您确定要清除已批准的网站吗?" + }, "contractInteraction": { "message": "合约交互" }, + "clearApprovalData": { + "message": "清除批准数据" + }, "reject": { "message": "拒绝" }, - "likeToConnect": { + "providerRequest": { "message": "$1 希望关联您的账户" }, + "providerRequestInfo": { + "message": "下面列出的域正在尝试请求访问Ethereum API,以便它可以与以太坊区块链进行交互。在批准Ethereum访问之前,请务必仔细检查您是否在正确的站点上。" + }, "about": { "message": "关于" }, @@ -75,7 +87,7 @@ "message": "添加推荐代币" }, "addAcquiredTokens": { - "message": "在MetaMask上添加已用的代币" + "message": "在Metamask上添加已用的代币" }, "amount": { "message": "数量" @@ -145,6 +157,10 @@ "basic": { "message": "基本" }, + "betweenMinAndMax": { + "message": "必须大于等于 $1 并且小于等于 $2 。", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerUrl": { "message": "屏蔽管理器" }, @@ -236,6 +252,9 @@ "connect": { "message": "连接" }, + "connectRequest": { + "message": "关联请求" + }, "connectingTo": { "message": "正在连接 $1" }, @@ -275,6 +294,9 @@ "copiedExclamation": { "message": "已复制" }, + "copy": { + "message": "复制" + }, "copyAddress": { "message": "将地址复制到剪贴板" }, @@ -353,6 +375,9 @@ "directDepositEtherExplainer": { "message": "如果你已经有了一些 Ether,通过直接转入是你的新钱包获取 Ether 的最快捷方式。" }, + "dismiss": { + "message": "关闭" + }, "done": { "message": "完成" }, @@ -512,6 +537,10 @@ "getStarted": { "message": "开始使用" }, + "greaterThanMin": { + "message": "必须要大于等于 $1。", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "我们很高兴与您见面。" }, @@ -627,6 +656,10 @@ "ledgerAccountRestriction": { "message": "请在新增账户前,确认添加上一个账户。" }, + "lessThanMax": { + "message": "必须小于或等于 $1.", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "是的。立即开始设置!" }, @@ -819,6 +852,9 @@ "message": "请粘贴你的私钥:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "请粘贴你的助记词!" + }, "pending": { "message": "待处理" }, @@ -1132,6 +1168,9 @@ "storePhrase": { "message": "通过如 1Password 等密码管家保存该密语。" }, + "submit": { + "message": "提交" + }, "submitted": { "message": "已提交" }, @@ -1296,6 +1335,9 @@ "userName": { "message": "用户名" }, + "validFileImport": { + "message": "您必须选择一个有效的文件进行导入." + }, "viewAccount": { "message": "查看账户" }, diff --git a/app/_locales/zh_TW/messages.json b/app/_locales/zh_TW/messages.json index 2859e8fd4..501272799 100644 --- a/app/_locales/zh_TW/messages.json +++ b/app/_locales/zh_TW/messages.json @@ -1,16 +1,28 @@ { + "privacyModeDefault": { + "message": "隱私模式現已根據預設開啟" + }, "chartOnlyAvailableEth": { "message": "圖表僅適用於以太坊網路。" }, + "confirmClear": { + "message": "您確定要清除已批准的網站紀錄?" + }, "contractInteraction": { "message": "合約互動" }, + "clearApprovalData": { + "message": "清除批准數據" + }, "reject": { "message": "拒絕" }, - "likeToConnect": { + "providerRequest": { "message": "$1 請求訪問帳戶權限" }, + "providerRequestInfo": { + "message": "此網站希望能讀取您的帳戶資訊。請務必確認您信任這個網站、並了解後續可能的交易行為。" + }, "about": { "message": "關於" }, @@ -145,6 +157,10 @@ "basic": { "message": "基本" }, + "betweenMinAndMax": { + "message": "必須大於等於 $1 並且小於等於 $2 。", + "description": "helper for inputting hex as decimal input" + }, "blockExplorerUrl": { "message": "區塊鏈瀏覽器" }, @@ -233,6 +249,9 @@ "connect": { "message": "連線" }, + "connectRequest": { + "message": "連線請求" + }, "connectingTo": { "message": "連線到$1" }, @@ -272,6 +291,9 @@ "copiedExclamation": { "message": "已複製!" }, + "copy": { + "message": "複製" + }, "copyAddress": { "message": "複製到剪貼簿" }, @@ -350,6 +372,9 @@ "directDepositEtherExplainer": { "message": "如果您已經擁有乙太幣,直接存入功能是讓新錢包最快取得乙太幣的方式。" }, + "dismiss": { + "message": "關閉" + }, "done": { "message": "完成" }, @@ -515,6 +540,10 @@ "getStarted": { "message": "開始使用" }, + "greaterThanMin": { + "message": "必須要大於等於 $1。", + "description": "helper for inputting hex as decimal input" + }, "happyToSeeYou": { "message": "我們很高興看到你。" }, @@ -633,6 +662,10 @@ "ledgerAccountRestriction": { "message": "您必須使用最後的帳戶才能產生新帳戶" }, + "lessThanMax": { + "message": "必須小於等於 $1.", + "description": "helper for inputting hex as decimal input" + }, "letsGoSetUp": { "message": "好,我們開始吧!" }, @@ -825,6 +858,9 @@ "message": "請貼上您的私鑰字串:", "description": "For importing an account from a private key" }, + "pasteSeed": { + "message": "請貼上您的助憶詞!" + }, "pending": { "message": "等待處理" }, @@ -1126,6 +1162,9 @@ "storePhrase": { "message": "您可以用密碼管理系統例如 1Password 等軟體儲存助憶詞。" }, + "submit": { + "message": "送出" + }, "submitted": { "message": "已送出" }, @@ -1290,6 +1329,9 @@ "userName": { "message": "使用者名稱" }, + "validFileImport": { + "message": "您必須選擇一個合法的檔案來匯入." + }, "viewAccount": { "message": "查看帳戶" }, diff --git a/app/images/broken-line.svg b/app/images/broken-line.svg deleted file mode 100644 index ec4ed0d9c..000000000 --- a/app/images/broken-line.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/images/connect-white.svg b/app/images/connect-white.svg deleted file mode 100644 index e9063ae46..000000000 --- a/app/images/connect-white.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/images/permissions-check.svg b/app/images/provider-approval-check.svg similarity index 100% rename from app/images/permissions-check.svg rename to app/images/provider-approval-check.svg diff --git a/app/manifest.json b/app/manifest.json index 58d4e1308..f9e25ceda 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "short_name": "__MSG_appName__", - "version": "7.7.0", + "version": "7.6.1", "manifest_version": 2, "author": "https://metamask.io", "description": "__MSG_appDescription__", diff --git a/app/scripts/background.js b/app/scripts/background.js index e5c9ea665..2639d7703 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -64,7 +64,6 @@ const isEdge = !isIE && !!window.StyleMedia let popupIsOpen = false let notificationIsOpen = false const openMetamaskTabsIDs = {} -const requestAccountTabIds = {} // state persistence const localStore = new LocalStore() @@ -76,6 +75,7 @@ initialize().catch(log.error) // setup metamask mesh testing container const { submitMeshMetricsEntry } = setupMetamaskMeshMetrics() + /** * An object representing a transaction, in whatever state it is in. * @typedef TransactionMeta @@ -248,12 +248,6 @@ function setupController (initState, initLangCode) { // platform specific api platform, encryptor: isEdge ? new EdgeEncryptor() : undefined, - getRequestAccountTabIds: () => { - return requestAccountTabIds - }, - getOpenMetamaskTabsIds: () => { - return openMetamaskTabsIDs - }, }) const provider = controller.provider @@ -266,9 +260,7 @@ function setupController (initState, initLangCode) { // report failed transactions to Sentry controller.txController.on(`tx:status-update`, (txId, status) => { - if (status !== 'failed') { - return - } + if (status !== 'failed') return const txMeta = controller.txController.txStateManager.getTx(txId) try { reportFailedTxToSentry({ sentry, txMeta }) @@ -320,7 +312,6 @@ function setupController (initState, initLangCode) { // extension.runtime.onConnect.addListener(connectRemote) extension.runtime.onConnectExternal.addListener(connectExternal) - extension.runtime.onMessage.addListener(controller.onMessage.bind(controller)) const metamaskInternalProcessHash = { [ENVIRONMENT_TYPE_POPUP]: true, @@ -393,17 +384,6 @@ function setupController (initState, initLangCode) { }) } } else { - if (remotePort.sender && remotePort.sender.tab && remotePort.sender.url) { - const tabId = remotePort.sender.tab.id - const url = new URL(remotePort.sender.url) - const origin = url.hostname - - remotePort.onMessage.addListener(msg => { - if (msg.data && msg.data.method === 'eth_requestAccounts') { - requestAccountTabIds[origin] = tabId - } - }) - } connectExternal(remotePort) } } @@ -428,7 +408,7 @@ function setupController (initState, initLangCode) { controller.messageManager.on('updateBadge', updateBadge) controller.personalMessageManager.on('updateBadge', updateBadge) controller.typedMessageManager.on('updateBadge', updateBadge) - controller.permissionsController.permissions.subscribe(updateBadge) + controller.providerApprovalController.memStore.on('update', updateBadge) /** * Updates the Web Extension's "badge" number, on the little fox in the toolbar. @@ -440,8 +420,8 @@ function setupController (initState, initLangCode) { const unapprovedMsgCount = controller.messageManager.unapprovedMsgCount const unapprovedPersonalMsgs = controller.personalMessageManager.unapprovedPersonalMsgCount const unapprovedTypedMsgs = controller.typedMessageManager.unapprovedTypedMessagesCount - const pendingPermissionRequests = Object.keys(controller.permissionsController.permissions.state.permissionsRequests).length - const count = unapprovedTxCount + unapprovedMsgCount + unapprovedPersonalMsgs + unapprovedTypedMsgs + pendingPermissionRequests + const pendingProviderRequests = controller.providerApprovalController.memStore.getState().providerRequests.length + const count = unapprovedTxCount + unapprovedMsgCount + unapprovedPersonalMsgs + unapprovedTypedMsgs + pendingProviderRequests if (count) { label = String(count) } diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index d583399ae..db4d5fd63 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -2,8 +2,8 @@ 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 { Writable } = require('readable-stream') const LocalMessageDuplexStream = require('post-message-stream') const ObjectMultiplex = require('obj-multiplex') const extension = require('extensionizer') @@ -20,7 +20,7 @@ const inpageBundle = inpageContent + inpageSuffix // If we create a FireFox-only code path using that API, // MetaMask will be much faster loading and performant on Firefox. -if (shouldInjectProvider()) { +if (shouldInjectWeb3()) { injectScript(inpageBundle) start() } @@ -39,7 +39,7 @@ function injectScript (content) { container.insertBefore(scriptTag, container.children[0]) container.removeChild(scriptTag) } catch (e) { - console.error('MetaMask provider injection failed.', e) + console.error('MetaMask script injection failed', e) } } @@ -86,44 +86,6 @@ async function setupStreams () { (err) => logStreamDisconnectWarning('MetaMask Background Multiplex', err) ) - const onboardingStream = pageMux.createStream('onboarding') - const addCurrentTab = new Writable({ - objectMode: true, - write: (chunk, _, callback) => { - if (!chunk) { - return callback(new Error('Malformed onboarding message')) - } - - const handleSendMessageResponse = (error, success) => { - if (!error && !success) { - error = extension.runtime.lastError - } - if (error) { - log.error(`Failed to send ${chunk.type} message`, error) - return callback(error) - } - callback(null) - } - - try { - if (chunk.type === 'registerOnboarding') { - extension.runtime.sendMessage({ type: 'metamask:registerOnboarding', location: window.location.href }, handleSendMessageResponse) - } else { - throw new Error(`Unrecognized onboarding message type: '${chunk.type}'`) - } - } catch (error) { - log.error(error) - return callback(error) - } - }, - }) - - pump( - onboardingStream, - addCurrentTab, - error => console.error('MetaMask onboarding channel traffic failed', error), - ) - // forward communication across inpage-background for these channels only forwardTrafficBetweenMuxers('provider', pageMux, extensionMux) forwardTrafficBetweenMuxers('publicConfig', pageMux, extensionMux) @@ -131,6 +93,12 @@ async function setupStreams () { // connect "phishing" channel to warning system const phishingStream = extensionMux.createStream('phishing') phishingStream.once('data', redirectToPhishingWarning) + + // connect "publicApi" channel to submit page metadata + const publicApiStream = extensionMux.createStream('publicApi') + const background = await setupPublicApi(publicApiStream) + + return { background } } function forwardTrafficBetweenMuxers (channelName, muxA, muxB) { @@ -144,6 +112,37 @@ function forwardTrafficBetweenMuxers (channelName, muxA, muxB) { ) } +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 +} + +/** + * Gets site metadata and returns it + * + */ +function getSiteMetadata () { + // get metadata + const metadata = { + name: getSiteName(window), + icon: getSiteIcon(window), + } + return metadata +} + /** * Error handler for page to extension stream disconnections * @@ -152,18 +151,16 @@ function forwardTrafficBetweenMuxers (channelName, muxA, muxB) { */ function logStreamDisconnectWarning (remoteLabel, err) { let warningMsg = `MetamaskContentscript - lost connection to ${remoteLabel}` - if (err) { - warningMsg += '\n' + err.stack - } + if (err) warningMsg += '\n' + err.stack console.warn(warningMsg) } /** - * Determines if the provider should be injected + * Determines if Web3 should be injected * - * @returns {boolean} {@code true} if the provider should be injected + * @returns {boolean} {@code true} if Web3 should be injected */ -function shouldInjectProvider () { +function shouldInjectWeb3 () { return doctypeCheck() && suffixCheck() && documentElementCheck() && !blacklistedDomainCheck() } @@ -186,8 +183,8 @@ function doctypeCheck () { * Returns whether or not the extension (suffix) of the current document is prohibited * * This checks {@code window.location.pathname} against a set of file extensions - * that we should not inject the provider into. This check is indifferent of - * query parameters in the location. + * that should not have web3 injected into them. This check is indifferent of query parameters + * in the location. * * @returns {boolean} whether or not the extension of the current document is prohibited */ @@ -260,14 +257,52 @@ 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"]') + if (siteName) { + return siteName.content + } + + const metaTitle = document.querySelector('head > meta[name="title"]') + if (metaTitle) { + return metaTitle.content + } + + return document.title +} + +/** + * Extracts an icon for the site from the DOM + */ +function getSiteIcon (window) { + const document = window.document + + // Use the site's favicon if it exists + const shortcutIcon = document.querySelector('head > link[rel="shortcut icon"]') + if (shortcutIcon) { + return shortcutIcon.href + } + + // Search through available icons in no particular order + const icon = Array.from(document.querySelectorAll('head > link[rel="icon"]')).find((icon) => Boolean(icon.href)) + if (icon) { + return icon.href + } + + 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 - } + if (['interactive', 'complete'].includes(document.readyState)) return // wait for load - return new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve, { once: true })) + 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 index c60a1c4f5..8d67874ad 100644 --- a/app/scripts/controllers/app-state.js +++ b/app/scripts/controllers/app-state.js @@ -45,7 +45,7 @@ class AppStateController { * @private */ _setInactiveTimeout (timeoutMinutes) { - this.store.updateState({ + this.store.putState({ timeoutMinutes, }) diff --git a/app/scripts/controllers/detect-tokens.js b/app/scripts/controllers/detect-tokens.js index 923aa2d15..e6e993073 100644 --- a/app/scripts/controllers/detect-tokens.js +++ b/app/scripts/controllers/detect-tokens.js @@ -29,12 +29,8 @@ class DetectTokensController { * */ async detectNewTokens () { - if (!this.isActive) { - return - } - if (this._network.store.getState().provider.type !== MAINNET) { - return - } + if (!this.isActive) { return } + if (this._network.store.getState().provider.type !== MAINNET) { return } const tokensToDetect = [] this.web3.setProvider(this._network._provider) for (const contractAddress in contracts) { @@ -84,9 +80,7 @@ class DetectTokensController { * */ restartTokenDetection () { - if (!(this.isActive && this.selectedAddress)) { - return - } + if (!(this.isActive && this.selectedAddress)) { return } this.detectNewTokens() this.interval = DEFAULT_INTERVAL } @@ -96,12 +90,8 @@ class DetectTokensController { */ set interval (interval) { this._handle && clearInterval(this._handle) - if (!interval) { - return - } - this._handle = setInterval(() => { - this.detectNewTokens() - }, interval) + if (!interval) { return } + this._handle = setInterval(() => { this.detectNewTokens() }, interval) } /** @@ -109,15 +99,9 @@ class DetectTokensController { * @type {Object} */ set preferences (preferences) { - if (!preferences) { - return - } + if (!preferences) { return } this._preferences = preferences - preferences.store.subscribe(({ tokens = [] }) => { - this.tokenAddresses = tokens.map((obj) => { - return obj.address - }) - }) + preferences.store.subscribe(({ tokens = [] }) => { this.tokenAddresses = tokens.map((obj) => { return obj.address }) }) preferences.store.subscribe(({ selectedAddress }) => { if (this.selectedAddress !== selectedAddress) { this.selectedAddress = selectedAddress @@ -130,9 +114,7 @@ class DetectTokensController { * @type {Object} */ set network (network) { - if (!network) { - return - } + if (!network) { return } this._network = network this.web3 = new Web3(network._provider) } @@ -142,16 +124,12 @@ class DetectTokensController { * @type {Object} */ set keyringMemStore (keyringMemStore) { - if (!keyringMemStore) { - return - } + if (!keyringMemStore) { return } this._keyringMemStore = keyringMemStore this._keyringMemStore.subscribe(({ isUnlocked }) => { if (this.isUnlocked !== isUnlocked) { this.isUnlocked = isUnlocked - if (isUnlocked) { - this.restartTokenDetection() - } + if (isUnlocked) { this.restartTokenDetection() } } }) } diff --git a/app/scripts/controllers/incoming-transactions.js b/app/scripts/controllers/incoming-transactions.js index 10735b3f9..029bf47aa 100644 --- a/app/scripts/controllers/incoming-transactions.js +++ b/app/scripts/controllers/incoming-transactions.js @@ -163,9 +163,7 @@ class IncomingTransactionsController { const newIncomingTransactions = { ...currentIncomingTxs, } - newTxs.forEach(tx => { - newIncomingTransactions[tx.hash] = tx - }) + newTxs.forEach(tx => { newIncomingTransactions[tx.hash] = tx }) this.store.updateState({ incomingTxLastFetchedBlocksByNetwork: { diff --git a/app/scripts/controllers/network/middleware/pending.js b/app/scripts/controllers/network/middleware/pending.js index 96a5d40be..542d8bde6 100644 --- a/app/scripts/controllers/network/middleware/pending.js +++ b/app/scripts/controllers/network/middleware/pending.js @@ -4,13 +4,9 @@ const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware function createPendingNonceMiddleware ({ getPendingNonce }) { return createAsyncMiddleware(async (req, res, next) => { const {method, params} = req - if (method !== 'eth_getTransactionCount') { - return next() - } + if (method !== 'eth_getTransactionCount') return next() const [param, blockRef] = params - if (blockRef !== 'pending') { - return next() - } + if (blockRef !== 'pending') return next() res.result = await getPendingNonce(param) }) } @@ -18,14 +14,10 @@ function createPendingNonceMiddleware ({ getPendingNonce }) { function createPendingTxMiddleware ({ getPendingTransactionByHash }) { return createAsyncMiddleware(async (req, res, next) => { const {method, params} = req - if (method !== 'eth_getTransactionByHash') { - return next() - } + if (method !== 'eth_getTransactionByHash') return next() const [hash] = params const txMeta = getPendingTransactionByHash(hash) - if (!txMeta) { - return next() - } + if (!txMeta) return next() res.result = formatTxMetaForRpcResult(txMeta) }) } diff --git a/app/scripts/controllers/network/network.js b/app/scripts/controllers/network/network.js index d78f92f93..7b5d8ef38 100644 --- a/app/scripts/controllers/network/network.js +++ b/app/scripts/controllers/network/network.js @@ -81,9 +81,7 @@ module.exports = class NetworkController extends EventEmitter { verifyNetwork () { // Check network when restoring connectivity: - if (this.isNetworkLoading()) { - this.lookupNetwork() - } + if (this.isNetworkLoading()) this.lookupNetwork() } getNetworkState () { @@ -198,7 +196,7 @@ module.exports = class NetworkController extends EventEmitter { }) this._setNetworkClient(networkClient) // setup networkConfig - const settings = { + var settings = { ticker: 'ETH', } this.networkConfig.putState(settings) @@ -221,7 +219,7 @@ module.exports = class NetworkController extends EventEmitter { nickname, } // setup networkConfig - let settings = { + var settings = { network: chainId, } settings = extend(settings, networks.networkList['rpc']) diff --git a/app/scripts/controllers/onboarding.js b/app/scripts/controllers/onboarding.js index 5d00fb775..a29c8407a 100644 --- a/app/scripts/controllers/onboarding.js +++ b/app/scripts/controllers/onboarding.js @@ -1,6 +1,5 @@ const ObservableStore = require('obs-store') const extend = require('xtend') -const log = require('loglevel') /** * @typedef {Object} InitState @@ -10,12 +9,11 @@ const log = require('loglevel') /** * @typedef {Object} OnboardingOptions * @property {InitState} initState The initial controller state - * @property {PreferencesController} preferencesController Controller for managing user perferences */ /** * Controller responsible for maintaining - * state related to onboarding + * a cache of account balances in local storage */ class OnboardingController { /** @@ -24,28 +22,10 @@ class OnboardingController { * @param {OnboardingOptions} [opts] Controller configuration parameters */ constructor (opts = {}) { - const initialTransientState = { - onboardingTabs: {}, - } - const initState = extend( - { - seedPhraseBackedUp: true, - }, - opts.initState, - initialTransientState, - ) + const initState = extend({ + seedPhraseBackedUp: true, + }, opts.initState) this.store = new ObservableStore(initState) - this.preferencesController = opts.preferencesController - this.completedOnboarding = this.preferencesController.store.getState().completedOnboarding - - this.preferencesController.store.subscribe(({ completedOnboarding }) => { - if (completedOnboarding !== this.completedOnboarding) { - this.completedOnboarding = completedOnboarding - if (completedOnboarding) { - this.store.updateState(initialTransientState) - } - } - }) } setSeedPhraseBackedUp (newSeedPhraseBackUpState) { @@ -58,24 +38,6 @@ class OnboardingController { return this.store.getState().seedPhraseBackedUp } - /** - * Registering a site as having initiated onboarding - * - * @param {string} location - The location of the site registering - * @param {string} tabId - The id of the tab registering - */ - async registerOnboarding (location, tabId) { - if (this.completedOnboarding) { - log.debug('Ignoring registerOnboarding; user already onboarded') - return - } - const onboardingTabs = Object.assign({}, this.store.getState().onboardingTabs) - if (!onboardingTabs[location] || onboardingTabs[location] !== tabId) { - log.debug(`Registering onboarding tab at location '${location}' with tabId '${tabId}'`) - onboardingTabs[location] = tabId - this.store.updateState({ onboardingTabs }) - } - } } module.exports = OnboardingController diff --git a/app/scripts/controllers/permissions/index.js b/app/scripts/controllers/permissions/index.js deleted file mode 100644 index 899044b13..000000000 --- a/app/scripts/controllers/permissions/index.js +++ /dev/null @@ -1,377 +0,0 @@ -const JsonRpcEngine = require('json-rpc-engine') -const asMiddleware = require('json-rpc-engine/src/asMiddleware') -const ObservableStore = require('obs-store') -const RpcCap = require('rpc-cap').CapabilitiesController -const { ethErrors } = require('eth-json-rpc-errors') - -const getRestrictedMethods = require('./restrictedMethods') -const createMethodMiddleware = require('./methodMiddleware') -const createLoggerMiddleware = require('./loggerMiddleware') - -// Methods that do not require any permissions to use: -const SAFE_METHODS = require('./permissions-safe-methods.json') - -// some constants -const METADATA_STORE_KEY = 'domainMetadata' -const LOG_STORE_KEY = 'permissionsLog' -const HISTORY_STORE_KEY = 'permissionsHistory' -const WALLET_METHOD_PREFIX = 'wallet_' -const CAVEAT_NAMES = { - exposedAccounts: 'exposedAccounts', -} -const ACCOUNTS_CHANGED_NOTIFICATION = 'wallet_accountsChanged' - -class PermissionsController { - - constructor ( - { - platform, notifyDomain, notifyAllDomains, keyringController, - } = {}, - restoredPermissions = {}, - restoredState = {}) { - this.store = new ObservableStore({ - [METADATA_STORE_KEY]: restoredState[METADATA_STORE_KEY] || {}, - [LOG_STORE_KEY]: restoredState[LOG_STORE_KEY] || [], - [HISTORY_STORE_KEY]: restoredState[HISTORY_STORE_KEY] || {}, - }) - this.notifyDomain = notifyDomain - this.notifyAllDomains = notifyAllDomains - this.keyringController = keyringController - this._platform = platform - this._restrictedMethods = getRestrictedMethods(this) - this._initializePermissions(restoredPermissions) - } - - createMiddleware ({ origin, extensionId }) { - - if (extensionId) { - this.store.updateState({ - [METADATA_STORE_KEY]: { - ...this.store.getState()[METADATA_STORE_KEY], - [origin]: { extensionId }, - }, - }) - } - - const engine = new JsonRpcEngine() - - engine.push(createLoggerMiddleware({ - walletPrefix: WALLET_METHOD_PREFIX, - restrictedMethods: Object.keys(this._restrictedMethods), - ignoreMethods: [ 'wallet_sendDomainMetadata' ], - store: this.store, - logStoreKey: LOG_STORE_KEY, - historyStoreKey: HISTORY_STORE_KEY, - })) - - engine.push(createMethodMiddleware({ - store: this.store, - storeKey: METADATA_STORE_KEY, - getAccounts: this.getAccounts.bind(this, origin), - requestAccountsPermission: this._requestPermissions.bind( - this, origin, { eth_accounts: {} } - ), - })) - - engine.push(this.permissions.providerMiddlewareFunction.bind( - this.permissions, { origin } - )) - return asMiddleware(engine) - } - - /** - * Returns the accounts that should be exposed for the given origin domain, - * if any. This method exists for when a trusted context needs to know - * which accounts are exposed to a given domain. - * - * @param {string} origin - The origin string. - */ - getAccounts (origin) { - return new Promise((resolve, _) => { - - const req = { method: 'eth_accounts' } - const res = {} - this.permissions.providerMiddlewareFunction( - { origin }, req, res, () => {}, _end - ) - - function _end () { - if (res.error || !Array.isArray(res.result)) { - resolve([]) - } else { - resolve(res.result) - } - } - }) - } - - /** - * Submits a permissions request to rpc-cap. Internal use only. - * - * @param {string} origin - The origin string. - * @param {IRequestedPermissions} permissions - The requested permissions. - */ - _requestPermissions (origin, permissions) { - return new Promise((resolve, reject) => { - - const req = { method: 'wallet_requestPermissions', params: [permissions] } - const res = {} - this.permissions.providerMiddlewareFunction( - { origin }, req, res, () => {}, _end - ) - - function _end (err) { - if (err || res.error) { - reject(err || res.error) - } else { - resolve(res.result) - } - } - }) - } - - /** - * User approval callback. The request can fail if the request is invalid. - * - * @param {object} approved the approved request object - */ - async approvePermissionsRequest (approved, accounts) { - - const { id } = approved.metadata - const approval = this.pendingApprovals[id] - - try { - - // attempt to finalize the request and resolve it - await this.finalizePermissionsRequest(approved.permissions, accounts) - approval.resolve(approved.permissions) - - } catch (err) { - - // if finalization fails, reject the request - approval.reject(ethErrors.rpc.invalidRequest({ - message: err.message, data: err, - })) - } - - delete this.pendingApprovals[id] - } - - /** - * User rejection callback. - * - * @param {string} id the id of the rejected request - */ - async rejectPermissionsRequest (id) { - const approval = this.pendingApprovals[id] - approval.reject(ethErrors.provider.userRejectedRequest()) - delete this.pendingApprovals[id] - } - - /** - * Grants the given origin the eth_accounts permission for the given account(s). - * This method should ONLY be called as a result of direct user action in the UI, - * with the intention of supporting legacy dapps that don't support EIP 1102. - * - * @param {string} origin - The origin to expose the account(s) to. - * @param {Array} accounts - The account(s) to expose. - */ - async legacyExposeAccounts (origin, accounts) { - - const permissions = { - eth_accounts: {}, - } - - await this.finalizePermissionsRequest(permissions, accounts) - - let error - try { - await new Promise((resolve, reject) => { - this.permissions.grantNewPermissions(origin, permissions, {}, err => err ? resolve() : reject(err)) - }) - } catch (err) { - error = err - } - - if (error) { - if (error.code === 4001) { - throw error - } else { - throw ethErrors.rpc.internal({ - message: `Failed to add 'eth_accounts' to '${origin}'.`, - data: { - originalError: error, - accounts, - }, - }) - } - } - } - - /** - * Update the accounts exposed to the given origin. - * Throws error if the update fails. - * - * @param {string} origin - The origin to change the exposed accounts for. - * @param {string[]} accounts - The new account(s) to expose. - */ - async updateExposedAccounts (origin, accounts) { - - await this.validateExposedAccounts(accounts) - - this.permissions.updateCaveatFor( - origin, 'eth_accounts', CAVEAT_NAMES.exposedAccounts, accounts - ) - - this.notifyDomain(origin, { - method: ACCOUNTS_CHANGED_NOTIFICATION, - result: accounts, - }) - } - - /** - * Finalizes a permissions request. - * Throws if request validation fails. - * - * @param {Object} requestedPermissions - The requested permissions. - * @param {string[]} accounts - The accounts to expose, if any. - */ - async finalizePermissionsRequest (requestedPermissions, accounts) { - - const { eth_accounts: ethAccounts } = requestedPermissions - - if (ethAccounts) { - - await this.validateExposedAccounts(accounts) - - if (!ethAccounts.caveats) { - ethAccounts.caveats = [] - } - - // caveat names are unique, and we will only construct this caveat here - ethAccounts.caveats = ethAccounts.caveats.filter(c => ( - c.name !== CAVEAT_NAMES.exposedAccounts - )) - - ethAccounts.caveats.push( - { - type: 'filterResponse', - value: accounts, - name: CAVEAT_NAMES.exposedAccounts, - }, - ) - } - } - - /** - * Validate an array of accounts representing accounts to be exposed - * to a domain. Throws error if validation fails. - * - * @param {string[]} accounts - An array of addresses. - */ - async validateExposedAccounts (accounts) { - - if (!Array.isArray(accounts) || accounts.length === 0) { - throw new Error('Must provide non-empty array of account(s).') - } - - // assert accounts exist - const allAccounts = await this.keyringController.getAccounts() - accounts.forEach(acc => { - if (!allAccounts.includes(acc)) { - throw new Error(`Unknown account: ${acc}`) - } - }) - } - - /** - * Removes the given permissions for the given domain. - * @param {object} domains { origin: [permissions] } - */ - removePermissionsFor (domains) { - - Object.entries(domains).forEach(([origin, perms]) => { - - this.permissions.removePermissionsFor( - origin, - perms.map(methodName => { - - if (methodName === 'eth_accounts') { - this.notifyDomain( - origin, - { method: ACCOUNTS_CHANGED_NOTIFICATION, result: [] } - ) - } - - return { parentCapability: methodName } - }) - ) - }) - } - - /** - * Removes all known domains and their related permissions. - */ - clearPermissions () { - this.permissions.clearDomains() - this.notifyAllDomains({ - method: ACCOUNTS_CHANGED_NOTIFICATION, - result: [], - }) - } - - /** - * A convenience method for retrieving a login object - * or creating a new one if needed. - * - * @param {string} origin = The origin string representing the domain. - */ - _initializePermissions (restoredState) { - - // these permission requests are almost certainly stale - const initState = { ...restoredState, permissionsRequests: [] } - - this.pendingApprovals = {} - - this.permissions = new RpcCap({ - - // Supports passthrough methods: - safeMethods: SAFE_METHODS, - - // optional prefix for internal methods - methodPrefix: WALLET_METHOD_PREFIX, - - restrictedMethods: this._restrictedMethods, - - /** - * A promise-returning callback used to determine whether to approve - * permissions requests or not. - * - * Currently only returns a boolean, but eventually should return any - * specific parameters or amendments to the permissions. - * - * @param {string} req - The internal rpc-cap user request object. - */ - requestUserApproval: async (req) => { - const { metadata: { id } } = req - - this._platform.openExtensionInBrowser('connect') - - return new Promise((resolve, reject) => { - this.pendingApprovals[id] = { resolve, reject } - }) - }, - }, initState) - } -} - -module.exports = { - PermissionsController, - addInternalMethodPrefix: prefix, - CAVEAT_NAMES, -} - - -function prefix (method) { - return WALLET_METHOD_PREFIX + method -} diff --git a/app/scripts/controllers/permissions/loggerMiddleware.js b/app/scripts/controllers/permissions/loggerMiddleware.js deleted file mode 100644 index 0e3c7f393..000000000 --- a/app/scripts/controllers/permissions/loggerMiddleware.js +++ /dev/null @@ -1,169 +0,0 @@ - -const clone = require('clone') -const { isValidAddress } = require('ethereumjs-util') - -const LOG_LIMIT = 100 - -/** - * Create middleware for logging requests and responses to restricted and - * permissions-related methods. - */ -module.exports = function createLoggerMiddleware ({ - walletPrefix, restrictedMethods, store, logStoreKey, historyStoreKey, ignoreMethods, -}) { - return (req, res, next, _end) => { - let activityEntry, requestedMethods - const { origin, method } = req - const isInternal = method.startsWith(walletPrefix) - if ((isInternal || restrictedMethods.includes(method)) && !ignoreMethods.includes(method)) { - activityEntry = logActivity(req, isInternal) - if (method === `${walletPrefix}requestPermissions`) { - requestedMethods = getRequestedMethods(req) - } - } else if (method === 'eth_requestAccounts') { - activityEntry = logActivity(req, isInternal) - requestedMethods = [ 'eth_accounts' ] - } else { - return next() - } - - next(cb => { - const time = Date.now() - addResponse(activityEntry, res, time) - if (!res.error && requestedMethods) { - logHistory(requestedMethods, origin, res.result, time, method === 'eth_requestAccounts') - } - cb() - }) - } - - function logActivity (request, isInternal) { - const activityEntry = { - id: request.id, - method: request.method, - methodType: isInternal ? 'internal' : 'restricted', - origin: request.origin, - request: cloneObj(request), - requestTime: Date.now(), - response: null, - responseTime: null, - success: null, - } - commitActivity(activityEntry) - return activityEntry - } - - function addResponse (activityEntry, response, time) { - if (!response) { - return - } - activityEntry.response = cloneObj(response) - activityEntry.responseTime = time - activityEntry.success = !response.error - } - - function commitActivity (entry) { - const logs = store.getState()[logStoreKey] - if (logs.length > LOG_LIMIT - 2) { - logs.pop() - } - logs.push(entry) - store.updateState({ [logStoreKey]: logs }) - } - - function getRequestedMethods (request) { - if ( - !request.params || - typeof request.params[0] !== 'object' || - Array.isArray(request.params[0]) - ) { - return null - } - return Object.keys(request.params[0]) - } - - function logHistory (requestedMethods, origin, result, time, isEthRequestAccounts) { - let accounts, entries - if (isEthRequestAccounts) { - accounts = result - const accountToTimeMap = accounts.reduce((acc, account) => ({ ...acc, [account]: time }), {}) - entries = { 'eth_accounts': { accounts: accountToTimeMap, lastApproved: time } } - } else { - entries = result - ? result - .map(perm => { - if (perm.parentCapability === 'eth_accounts') { - accounts = getAccountsFromPermission(perm) - } - return perm.parentCapability - }) - .reduce((acc, m) => { - if (requestedMethods.includes(m)) { - if (m === 'eth_accounts') { - const accountToTimeMap = accounts.reduce((acc, account) => ({ ...acc, [account]: time }), {}) - acc[m] = { lastApproved: time, accounts: accountToTimeMap } - } else { - acc[m] = { lastApproved: time } - } - } - return acc - }, {}) - : {} - } - - if (Object.keys(entries).length > 0) { - commitHistory(origin, entries) - } - } - - function commitHistory (origin, entries) { - const history = store.getState()[historyStoreKey] || {} - const newOriginHistory = { - ...history[origin], - ...entries, - } - - if (history[origin] && history[origin]['eth_accounts'] && entries['eth_accounts']) { - newOriginHistory['eth_accounts'] = { - lastApproved: entries['eth_accounts'].lastApproved, - accounts: { - ...history[origin]['eth_accounts'].accounts, - ...entries['eth_accounts'].accounts, - }, - } - } - - history[origin] = newOriginHistory - - store.updateState({ [historyStoreKey]: history }) - } -} - -// the call to clone is set to disallow circular references -// we attempt cloning at a depth of 3 and 2, then return a -// shallow copy of the object -function cloneObj (obj) { - for (let i = 3; i > 1; i--) { - try { - return clone(obj, false, i) - } catch (_) {} - } - return { ...obj } -} - -function getAccountsFromPermission (perm) { - if (perm.parentCapability !== 'eth_accounts' || !perm.caveats) { - return [] - } - const accounts = {} - for (const c of perm.caveats) { - if (c.type === 'filterResponse' && Array.isArray(c.value)) { - for (const v of c.value) { - if (isValidAddress(v)) { - accounts[v] = true - } - } - } - } - return Object.keys(accounts) -} diff --git a/app/scripts/controllers/permissions/methodMiddleware.js b/app/scripts/controllers/permissions/methodMiddleware.js deleted file mode 100644 index e0b451c9f..000000000 --- a/app/scripts/controllers/permissions/methodMiddleware.js +++ /dev/null @@ -1,90 +0,0 @@ - -const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware') -const { ethErrors } = require('eth-json-rpc-errors') - -/** - * Create middleware for handling certain methods and preprocessing permissions requests. - */ -module.exports = function createMethodMiddleware ({ - store, storeKey, getAccounts, requestAccountsPermission, -}) { - return createAsyncMiddleware(async (req, res, next) => { - - if (typeof req.method !== 'string') { - res.error = ethErrors.rpc.invalidRequest({ data: req}) - return - } - - switch (req.method) { - - // intercepting eth_accounts requests for backwards compatibility, - // i.e. return an empty array instead of an error - case 'eth_accounts': - - res.result = await getAccounts() - return - - case 'eth_requestAccounts': - - // first, just try to get accounts - let accounts = await getAccounts() - if (accounts.length > 0) { - res.result = accounts - return - } - - // if no accounts, request the accounts permission - try { - await requestAccountsPermission() - } catch (err) { - res.error = err - return - } - - // get the accounts again - accounts = await getAccounts() - if (accounts.length > 0) { - res.result = accounts - } else { - // this should never happen - res.error = ethErrors.rpc.internal( - 'Accounts unexpectedly unavailable. Please report this bug.' - ) - } - - return - - // custom method for getting metadata from the requesting domain - case 'wallet_sendDomainMetadata': - - const storeState = store.getState()[storeKey] - const extensionId = storeState[req.origin] - ? storeState[req.origin].extensionId - : undefined - - if ( - req.domainMetadata && - typeof req.domainMetadata.name === 'string' - ) { - - store.updateState({ - [storeKey]: { - ...storeState, - [req.origin]: { - extensionId, - ...req.domainMetadata, - }, - }, - }) - } - - res.result = true - return - - default: - break - } - - next() - }) -} diff --git a/app/scripts/controllers/permissions/permissions-safe-methods.json b/app/scripts/controllers/permissions/permissions-safe-methods.json deleted file mode 100644 index 17b46b531..000000000 --- a/app/scripts/controllers/permissions/permissions-safe-methods.json +++ /dev/null @@ -1,49 +0,0 @@ -[ - "web3_sha3", - "net_listening", - "net_peerCount", - "net_version", - "eth_blockNumber", - "eth_call", - "eth_chainId", - "eth_coinbase", - "eth_estimateGas", - "eth_gasPrice", - "eth_getBalance", - "eth_getBlockByHash", - "eth_getBlockByNumber", - "eth_getBlockTransactionCountByHash", - "eth_getBlockTransactionCountByNumber", - "eth_getCode", - "eth_getFilterChanges", - "eth_getFilterLogs", - "eth_getLogs", - "eth_getStorageAt", - "eth_getTransactionByBlockHashAndIndex", - "eth_getTransactionByBlockNumberAndIndex", - "eth_getTransactionByHash", - "eth_getTransactionCount", - "eth_getTransactionReceipt", - "eth_getUncleByBlockHashAndIndex", - "eth_getUncleByBlockNumberAndIndex", - "eth_getUncleCountByBlockHash", - "eth_getUncleCountByBlockNumber", - "eth_getWork", - "eth_hashrate", - "eth_mining", - "eth_newBlockFilter", - "eth_newFilter", - "eth_newPendingTransactionFilter", - "eth_protocolVersion", - "eth_sendRawTransaction", - "eth_sendTransaction", - "eth_sign", - "personal_sign", - "eth_signTypedData", - "eth_signTypedData_v1", - "eth_signTypedData_v3", - "eth_submitHashrate", - "eth_submitWork", - "eth_syncing", - "eth_uninstallFilter" -] \ No newline at end of file diff --git a/app/scripts/controllers/permissions/restrictedMethods.js b/app/scripts/controllers/permissions/restrictedMethods.js deleted file mode 100644 index cd87b2d57..000000000 --- a/app/scripts/controllers/permissions/restrictedMethods.js +++ /dev/null @@ -1,20 +0,0 @@ - -module.exports = function getRestrictedMethods (permissionsController) { - return { - - 'eth_accounts': { - description: 'View the address of the selected account', - method: (_, res, __, end) => { - permissionsController.keyringController.getAccounts() - .then((accounts) => { - res.result = accounts - end() - }) - .catch((err) => { - res.error = err - end(err) - }) - }, - }, - } -} diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 409ce6876..1cfbb4d4c 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -1,5 +1,4 @@ const ObservableStore = require('obs-store') -const { addInternalMethodPrefix } = require('./permissions') const normalizeAddress = require('eth-sig-util').normalize const { isValidAddress, sha3, bufferToHex } = require('ethereumjs-util') const extend = require('xtend') @@ -58,6 +57,7 @@ class PreferencesController { useNativeCurrencyAsPrimaryCurrency: true, }, completedOnboarding: false, + migratedPrivacyMode: false, metaMetricsId: null, metaMetricsSendCount: 0, }, opts.initState) @@ -187,10 +187,7 @@ class PreferencesController { * @param {Function} - end */ async requestWatchAsset (req, res, next, end) { - if ( - req.method === 'metamask_watchAsset' || - req.method === addInternalMethodPrefix('watchAsset') - ) { + if (req.method === 'metamask_watchAsset' || req.method === 'wallet_watchAsset') { const { type, options } = req.params switch (type) { case 'ERC20': @@ -306,9 +303,7 @@ class PreferencesController { const accountTokens = this.store.getState().accountTokens addresses.forEach((address) => { // skip if already exists - if (identities[address]) { - return - } + if (identities[address]) return // add missing identity const identityCount = Object.keys(identities).length @@ -340,9 +335,7 @@ class PreferencesController { if (Object.keys(newlyLost).length > 0) { // Notify our servers: - if (this.diagnostics) { - this.diagnostics.reportOrphans(newlyLost) - } + if (this.diagnostics) this.diagnostics.reportOrphans(newlyLost) // store lost accounts for (const key in newlyLost) { @@ -470,9 +463,7 @@ class PreferencesController { * @return {Promise} */ setAccountLabel (account, label) { - if (!account) { - throw new Error('setAccountLabel requires a valid address, got ' + String(account)) - } + if (!account) throw new Error('setAccountLabel requires a valid address, got ' + String(account)) const address = normalizeAddress(account) const {identities} = this.store.getState() identities[address] = identities[address] || {} @@ -509,9 +500,7 @@ class PreferencesController { updateRpc (newRpcDetails) { const rpcList = this.getFrequentRpcListDetail() - const index = rpcList.findIndex((element) => { - return element.rpcUrl === newRpcDetails.rpcUrl - }) + const index = rpcList.findIndex((element) => { return element.rpcUrl === newRpcDetails.rpcUrl }) if (index > -1) { const rpcDetail = rpcList[index] const updatedRpc = extend(rpcDetail, newRpcDetails) @@ -535,9 +524,7 @@ class PreferencesController { */ addToFrequentRpcList (url, chainId, ticker = 'ETH', nickname = '', rpcPrefs = {}) { const rpcList = this.getFrequentRpcListDetail() - const index = rpcList.findIndex((element) => { - return element.rpcUrl === url - }) + const index = rpcList.findIndex((element) => { return element.rpcUrl === url }) if (index !== -1) { rpcList.splice(index, 1) } @@ -561,9 +548,7 @@ class PreferencesController { */ removeFromFrequentRpcList (url) { const rpcList = this.getFrequentRpcListDetail() - const index = rpcList.findIndex((element) => { - return element.rpcUrl === url - }) + const index = rpcList.findIndex((element) => { return element.rpcUrl === url }) if (index !== -1) { rpcList.splice(index, 1) } @@ -647,6 +632,13 @@ class PreferencesController { return Promise.resolve(true) } + unsetMigratedPrivacyMode () { + this.store.updateState({ + migratedPrivacyMode: false, + }) + return Promise.resolve() + } + // // PRIVATE METHODS // @@ -695,16 +687,10 @@ class PreferencesController { */ _getTokenRelatedStates (selectedAddress) { const accountTokens = this.store.getState().accountTokens - if (!selectedAddress) { - selectedAddress = this.store.getState().selectedAddress - } + if (!selectedAddress) selectedAddress = this.store.getState().selectedAddress const providerType = this.network.providerStore.getState().type - if (!(selectedAddress in accountTokens)) { - accountTokens[selectedAddress] = {} - } - if (!(providerType in accountTokens[selectedAddress])) { - accountTokens[selectedAddress][providerType] = [] - } + if (!(selectedAddress in accountTokens)) accountTokens[selectedAddress] = {} + if (!(providerType in accountTokens[selectedAddress])) accountTokens[selectedAddress][providerType] = [] const tokens = accountTokens[selectedAddress][providerType] return { tokens, accountTokens, providerType, selectedAddress } } @@ -741,19 +727,13 @@ class PreferencesController { */ _validateERC20AssetParams (opts) { const { rawAddress, symbol, decimals } = opts - if (!rawAddress || !symbol || typeof decimals === 'undefined') { - throw new Error(`Cannot suggest token without address, symbol, and decimals`) - } - if (!(symbol.length < 7)) { - throw new Error(`Invalid symbol ${symbol} more than six characters`) - } + if (!rawAddress || !symbol || typeof decimals === 'undefined') throw new Error(`Cannot suggest token without address, symbol, and decimals`) + if (!(symbol.length < 7)) throw new Error(`Invalid symbol ${symbol} more than six characters`) const numDecimals = parseInt(decimals, 10) if (isNaN(numDecimals) || numDecimals > 36 || numDecimals < 0) { throw new Error(`Invalid decimals ${decimals} must be at least 0, and not over 36`) } - if (!isValidAddress(rawAddress)) { - throw new Error(`Invalid address ${rawAddress}`) - } + if (!isValidAddress(rawAddress)) throw new Error(`Invalid address ${rawAddress}`) } } diff --git a/app/scripts/controllers/provider-approval.js b/app/scripts/controllers/provider-approval.js new file mode 100644 index 000000000..00ff626f7 --- /dev/null +++ b/app/scripts/controllers/provider-approval.js @@ -0,0 +1,175 @@ +const ObservableStore = require('obs-store') +const SafeEventEmitter = require('safe-event-emitter') +const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware') +const { errors: rpcErrors } = require('eth-json-rpc-errors') + +/** + * A controller that services user-approved requests for a full Ethereum provider API + */ +class ProviderApprovalController extends SafeEventEmitter { + /** + * Creates a ProviderApprovalController + * + * @param {Object} [config] - Options to configure controller + */ + constructor ({ closePopup, initState, keyringController, openPopup, preferencesController } = {}) { + super() + this.closePopup = closePopup + this.keyringController = keyringController + this.openPopup = openPopup + this.preferencesController = preferencesController + this.memStore = new ObservableStore({ + providerRequests: [], + }) + + const defaultState = { approvedOrigins: {} } + this.store = new ObservableStore(Object.assign(defaultState, initState)) + } + + /** + * 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 ({ senderUrl, extensionId, 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 + const origin = senderUrl.hostname + if (this.shouldExposeAccounts(origin) && isUnlocked) { + res.result = [this.preferencesController.getSelectedAddress()] + return + } + // register the provider request + const metadata = { hostname: senderUrl.hostname, origin } + if (extensionId) { + metadata.extensionId = extensionId + } else { + const siteMetadata = await getSiteMetadata(origin) + Object.assign(metadata, { siteTitle: siteMetadata.name, siteImage: siteMetadata.icon}) + } + this._handleProviderRequest(metadata) + // 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 rpcErrors.eth.userRejectedRequest('User denied account authorization') + } + }) + } + + /** + * @typedef {Object} SiteMetadata + * @param {string} hostname - The hostname of the site + * @param {string} origin - The origin of the site + * @param {string} [siteTitle] - The title of the site + * @param {string} [siteImage] - The icon for the site + * @param {string} [extensionId] - The extension ID of the extension + */ + /** + * Called when a tab requests access to a full Ethereum provider API + * + * @param {SiteMetadata} siteMetadata - The metadata for the site requesting full provider access + */ + _handleProviderRequest (siteMetadata) { + const { providerRequests } = this.memStore.getState() + const origin = siteMetadata.origin + this.memStore.updateState({ + providerRequests: [ + ...providerRequests, + siteMetadata, + ], + }) + const isUnlocked = this.keyringController.memStore.getState().isUnlocked + const { approvedOrigins } = this.store.getState() + const originAlreadyHandled = approvedOrigins[origin] + if (originAlreadyHandled && isUnlocked) { + return + } + this.openPopup && this.openPopup() + } + + /** + * Called when a user approves access to a full Ethereum provider API + * + * @param {string} origin - origin of the domain that had provider access approved + */ + approveProviderRequestByOrigin (origin) { + if (this.closePopup) { + this.closePopup() + } + + const { approvedOrigins } = this.store.getState() + const { providerRequests } = this.memStore.getState() + const providerRequest = providerRequests.find((request) => request.origin === origin) + const remainingProviderRequests = providerRequests.filter(request => request.origin !== origin) + this.store.updateState({ + approvedOrigins: { + ...approvedOrigins, + [origin]: { + siteTitle: providerRequest ? providerRequest.siteTitle : null, + siteImage: providerRequest ? providerRequest.siteImage : null, + hostname: providerRequest ? providerRequest.hostname : null, + }, + }, + }) + this.memStore.updateState({ providerRequests: remainingProviderRequests }) + this.emit(`resolvedRequest:${origin}`, { approved: true }) + } + + /** + * Called when a tab rejects access to a full Ethereum provider API + * + * @param {string} origin - origin of the domain that had provider access approved + */ + rejectProviderRequestByOrigin (origin) { + if (this.closePopup) { + this.closePopup() + } + + const { approvedOrigins } = this.store.getState() + const { providerRequests } = this.memStore.getState() + const remainingProviderRequests = providerRequests.filter(request => request.origin !== origin) + + // We're cloning and deleting keys here because we don't want to keep unneeded keys + const _approvedOrigins = Object.assign({}, approvedOrigins) + delete _approvedOrigins[origin] + + this.store.putState({ approvedOrigins: _approvedOrigins }) + this.memStore.putState({ providerRequests: remainingProviderRequests }) + this.emit(`resolvedRequest:${origin}`, { approved: false }) + } + + /** + * Clears any approvals for user-approved origins + */ + clearApprovedOrigins () { + this.store.updateState({ + approvedOrigins: {}, + }) + } + + /** + * Determines if a given origin should have accounts exposed + * + * @param {string} origin - Domain origin to check for approval status + * @returns {boolean} - True if the origin has been approved + */ + shouldExposeAccounts (origin) { + return Boolean(this.store.getState().approvedOrigins[origin]) + } + + /** + * Returns a merged state representation + * @return {object} + * @private + */ + _getMergedState () { + return Object.assign({}, this.memStore.getState(), this.store.getState()) + } +} + +module.exports = ProviderApprovalController diff --git a/app/scripts/controllers/recent-blocks.js b/app/scripts/controllers/recent-blocks.js index 9e5a384a9..a2b5d1bae 100644 --- a/app/scripts/controllers/recent-blocks.js +++ b/app/scripts/controllers/recent-blocks.js @@ -90,9 +90,7 @@ class RecentBlocksController { async processBlock (newBlockNumberHex) { const newBlockNumber = Number.parseInt(newBlockNumberHex, 16) const newBlock = await this.getBlockByNumber(newBlockNumber, true) - if (!newBlock) { - return - } + if (!newBlock) return const block = this.mapTransactionsToPrices(newBlock) @@ -164,9 +162,7 @@ class RecentBlocksController { await Promise.all(targetBlockNumbers.map(async (targetBlockNumber) => { try { const newBlock = await this.getBlockByNumber(targetBlockNumber, true) - if (!newBlock) { - return - } + if (!newBlock) return this.backfillBlock(newBlock) } catch (e) { diff --git a/app/scripts/controllers/threebox.js b/app/scripts/controllers/threebox.js index 8226fb6b1..5bcab29ed 100644 --- a/app/scripts/controllers/threebox.js +++ b/app/scripts/controllers/threebox.js @@ -28,9 +28,7 @@ class ThreeBoxController { this.provider = this._createProvider({ version, getAccounts: async ({ origin }) => { - if (origin !== '3Box') { - return [] - } + if (origin !== '3Box') { return [] } const isUnlocked = getKeyringControllerState().isUnlocked const accounts = await this.keyringController.getAccounts() diff --git a/app/scripts/controllers/token-rates.js b/app/scripts/controllers/token-rates.js index 7c8526c34..9b86a9ebf 100644 --- a/app/scripts/controllers/token-rates.js +++ b/app/scripts/controllers/token-rates.js @@ -28,9 +28,7 @@ class TokenRatesController { * Updates exchange rates for all tokens */ async updateExchangeRates () { - if (!this.isActive) { - return - } + if (!this.isActive) { return } const contractExchangeRates = {} const nativeCurrency = this.currency ? this.currency.state.nativeCurrency.toLowerCase() : 'eth' const pairs = this._tokens.map(token => token.address).join(',') @@ -55,12 +53,8 @@ class TokenRatesController { */ set interval (interval) { this._handle && clearInterval(this._handle) - if (!interval) { - return - } - this._handle = setInterval(() => { - this.updateExchangeRates() - }, interval) + if (!interval) { return } + this._handle = setInterval(() => { this.updateExchangeRates() }, interval) } /** @@ -68,14 +62,10 @@ class TokenRatesController { */ set preferences (preferences) { this._preferences && this._preferences.unsubscribe() - if (!preferences) { - return - } + if (!preferences) { return } this._preferences = preferences this.tokens = preferences.getState().tokens - preferences.subscribe(({ tokens = [] }) => { - this.tokens = tokens - }) + preferences.subscribe(({ tokens = [] }) => { this.tokens = tokens }) } /** diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index 85733aa38..df9fb6502 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -3,7 +3,7 @@ const ObservableStore = require('obs-store') const ethUtil = require('ethereumjs-util') const Transaction = require('ethereumjs-tx') const EthQuery = require('ethjs-query') -const { ethErrors } = require('eth-json-rpc-errors') +const { errors: rpcErrors } = require('eth-json-rpc-errors') const abi = require('human-standard-token-abi') const abiDecoder = require('abi-decoder') abiDecoder.addABI(abi) @@ -54,7 +54,6 @@ const { hexToBn, bnToHex, BnMultiplyByFraction } = require('../../lib/util') @param {Object} opts.blockTracker - An instance of eth-blocktracker @param {Object} opts.provider - A network provider. @param {Function} opts.signTransaction - function the signs an ethereumjs-tx - @param {object} opts.getPermittedAccounts - get accounts that an origin has permissions for @param {Function} [opts.getGasPrice] - optional gas price calculator @param {Function} opts.signTransaction - ethTx signer that returns a rawTx @param {Number} [opts.txHistoryLimit] - number *optional* for limiting how many transactions are in state @@ -67,7 +66,6 @@ class TransactionController extends EventEmitter { this.networkStore = opts.networkStore || new ObservableStore({}) this.preferencesStore = opts.preferencesStore || new ObservableStore({}) this.provider = opts.provider - this.getPermittedAccounts = opts.getPermittedAccounts this.blockTracker = opts.blockTracker this.signEthTx = opts.signTransaction this.getGasPrice = opts.getGasPrice @@ -135,7 +133,7 @@ class TransactionController extends EventEmitter { /** Adds a tx to the txlist @emits ${txMeta.id}:unapproved - */ +*/ addTx (txMeta) { this.txStateManager.addTx(txMeta) this.emit(`${txMeta.id}:unapproved`, txMeta) @@ -150,18 +148,18 @@ class TransactionController extends EventEmitter { } /** - * Add a new unapproved transaction to the pipeline - * - * @returns {Promise} the hash of the transaction after being submitted to the network - * @param txParams {object} - txParams for the transaction - * @param opts {object} - with the key origin to put the origin on the txMeta + add a new unapproved transaction to the pipeline + + @returns {Promise} the hash of the transaction after being submitted to the network + @param txParams {object} - txParams for the transaction + @param opts {object} - with the key origin to put the origin on the txMeta */ + async newUnapprovedTransaction (txParams, opts = {}) { - log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`) - - const initialTxMeta = await this.addUnapprovedTransaction(txParams, opts.origin) - + const initialTxMeta = await this.addUnapprovedTransaction(txParams) + initialTxMeta.origin = opts.origin + this.txStateManager.updateTx(initialTxMeta, '#newUnapprovedTransaction - adding the origin') // listen for tx completion (success, fail) return new Promise((resolve, reject) => { this.txStateManager.once(`${initialTxMeta.id}:finished`, (finishedTxMeta) => { @@ -169,64 +167,38 @@ class TransactionController extends EventEmitter { case 'submitted': return resolve(finishedTxMeta.hash) case 'rejected': - return reject(cleanErrorStack(ethErrors.provider.userRejectedRequest('MetaMask Tx Signature: User denied transaction signature.'))) + return reject(cleanErrorStack(rpcErrors.eth.userRejectedRequest('MetaMask Tx Signature: User denied transaction signature.'))) case 'failed': - return reject(cleanErrorStack(ethErrors.rpc.internal(finishedTxMeta.err.message))) + return reject(cleanErrorStack(rpcErrors.internal(finishedTxMeta.err.message))) default: - return reject(cleanErrorStack(ethErrors.rpc.internal(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(finishedTxMeta.txParams)}`))) + return reject(cleanErrorStack(rpcErrors.internal(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(finishedTxMeta.txParams)}`))) } }) }) } /** - * Validates and generates a txMeta with defaults and puts it in txStateManager - * store. - * - * @returns {txMeta} - */ - async addUnapprovedTransaction (txParams, origin) { + Validates and generates a txMeta with defaults and puts it in txStateManager + store + @returns {txMeta} + */ + + async addUnapprovedTransaction (txParams) { // validate const normalizedTxParams = txUtils.normalizeTxParams(txParams) - + // Assert the from address is the selected address + if (normalizedTxParams.from !== this.getSelectedAddress()) { + throw new Error(`Transaction from address isn't valid for this account`) + } txUtils.validateTxParams(normalizedTxParams) - /** - `generateTxMeta` adds the default txMeta properties to the passed object. - These include the tx's `id`. As we use the id for determining order of - txes in the tx-state-manager, it is necessary to call the asynchronous - method `this._determineTransactionCategory` after `generateTxMeta`. - */ + // construct txMeta + const { transactionCategory, getCodeResponse } = await this._determineTransactionCategory(txParams) let txMeta = this.txStateManager.generateTxMeta({ txParams: normalizedTxParams, type: TRANSACTION_TYPE_STANDARD, + transactionCategory, }) - - if (origin === 'metamask') { - // Assert the from address is the selected address - if (normalizedTxParams.from !== this.getSelectedAddress()) { - throw ethErrors.rpc.internal({ - message: `Internally initiated transaction is using invalid account.`, - data: { - origin, - fromAddress: normalizedTxParams.from, - selectedAddress: this.getSelectedAddress(), - }, - }) - } - } else { - // Assert that the origin has permissions to initiate transactions from - // the specified address - const permittedAddresses = await this.getPermittedAccounts(origin) - if (!permittedAddresses.includes(normalizedTxParams.from)) { - throw ethErrors.provider.unauthorized({ data: { origin }}) - } - } - - txMeta['origin'] = origin - - const { transactionCategory, getCodeResponse } = await this._determineTransactionCategory(txParams) - txMeta.transactionCategory = transactionCategory this.addTx(txMeta) this.emit('newUnapprovedTx', txMeta) @@ -245,16 +217,15 @@ class TransactionController extends EventEmitter { txMeta.loadingDefaults = false // save txMeta - this.txStateManager.updateTx(txMeta, 'Added new unapproved transaction.') + this.txStateManager.updateTx(txMeta) return txMeta } - /** - * Adds the tx gas defaults: gas && gasPrice - * @param txMeta {Object} - the txMeta object - * @returns {Promise} resolves with txMeta - */ + adds the tx gas defaults: gas && gasPrice + @param txMeta {Object} - the txMeta object + @returns {Promise} resolves with txMeta +*/ async addTxGasDefaults (txMeta, getCodeResponse) { const txParams = txMeta.txParams // ensure value @@ -431,16 +402,13 @@ class TransactionController extends EventEmitter { log.error(err) } // must set transaction to submitted/failed before releasing lock - if (nonceLock) { - nonceLock.releaseLock() - } + if (nonceLock) nonceLock.releaseLock() // continue with error chain throw err } finally { this.inProcessOfSigning.delete(txId) } } - /** adds the chain id and signs the transaction and set the status to signed @param txId {number} - the tx's Id @@ -635,9 +603,7 @@ class TransactionController extends EventEmitter { } }) this.pendingTxTracker.on('tx:retry', (txMeta) => { - if (!('retryCount' in txMeta)) { - txMeta.retryCount = 0 - } + if (!('retryCount' in txMeta)) txMeta.retryCount = 0 txMeta.retryCount++ this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:retry') }) @@ -691,14 +657,10 @@ class TransactionController extends EventEmitter { const txMeta = this.txStateManager.getTx(txId) const { nonce, from } = txMeta.txParams const sameNonceTxs = this.txStateManager.getFilteredTxList({nonce, from}) - if (!sameNonceTxs.length) { - return - } + if (!sameNonceTxs.length) return // mark all same nonce transactions as dropped and give i a replacedBy hash sameNonceTxs.forEach((otherTxMeta) => { - if (otherTxMeta.id === txId) { - return - } + if (otherTxMeta.id === txId) return otherTxMeta.replacedBy = txMeta.hash this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:confirmed reference to confirmed txHash with same nonce') this.txStateManager.setTxStatusDropped(otherTxMeta.id) diff --git a/app/scripts/controllers/transactions/lib/tx-state-history-helper.js b/app/scripts/controllers/transactions/lib/tx-state-history-helper.js index 3cc76e617..76fc5c35b 100644 --- a/app/scripts/controllers/transactions/lib/tx-state-history-helper.js +++ b/app/scripts/controllers/transactions/lib/tx-state-history-helper.js @@ -18,9 +18,7 @@ function migrateFromSnapshotsToDiffs (longHistory) { longHistory // convert non-initial history entries into diffs .map((entry, index) => { - if (index === 0) { - return entry - } + if (index === 0) return entry return generateHistoryEntry(longHistory[index - 1], entry) }) ) @@ -42,9 +40,7 @@ function generateHistoryEntry (previousState, newState, note) { const entry = jsonDiffer.compare(previousState, newState) // Add a note to the first op, since it breaks if we append it to the entry if (entry[0]) { - if (note) { - entry[0].note = note - } + if (note) entry[0].note = note entry[0].timestamp = Date.now() } diff --git a/app/scripts/controllers/transactions/lib/util.js b/app/scripts/controllers/transactions/lib/util.js index 86924e7fa..0d2ddddef 100644 --- a/app/scripts/controllers/transactions/lib/util.js +++ b/app/scripts/controllers/transactions/lib/util.js @@ -35,9 +35,7 @@ function normalizeTxParams (txParams, LowerCase) { // apply only keys in the normalizers const normalizedTxParams = {} for (const key in normalizers) { - if (txParams[key]) { - normalizedTxParams[key] = normalizers[key](txParams[key], LowerCase) - } + if (txParams[key]) normalizedTxParams[key] = normalizers[key](txParams[key], LowerCase) } return normalizedTxParams } @@ -66,12 +64,8 @@ function validateTxParams (txParams) { @param txParams {object} */ function validateFrom (txParams) { - if (!(typeof txParams.from === 'string')) { - throw new Error(`Invalid from address ${txParams.from} not a string`) - } - if (!isValidAddress(txParams.from)) { - throw new Error('Invalid from address') - } + if (!(typeof txParams.from === 'string')) throw new Error(`Invalid from address ${txParams.from} not a string`) + if (!isValidAddress(txParams.from)) throw new Error('Invalid from address') } /** diff --git a/app/scripts/controllers/transactions/pending-tx-tracker.js b/app/scripts/controllers/transactions/pending-tx-tracker.js index 4e2db5ead..8f4076f45 100644 --- a/app/scripts/controllers/transactions/pending-tx-tracker.js +++ b/app/scripts/controllers/transactions/pending-tx-tracker.js @@ -56,9 +56,7 @@ class PendingTransactionTracker extends EventEmitter { resubmitPendingTxs (blockNumber) { const pending = this.getPendingTransactions() // only try resubmitting if their are transactions to resubmit - if (!pending.length) { - return - } + if (!pending.length) return pending.forEach((txMeta) => this._resubmitTx(txMeta, blockNumber).catch((err) => { /* Dont marked as failed if the error is a "known" transaction warning @@ -81,9 +79,7 @@ class PendingTransactionTracker extends EventEmitter { errorMessage.includes('nonce too low') ) // ignore resubmit warnings, return early - if (isKnownTx) { - return - } + if (isKnownTx) return // encountered real error - transition to error state txMeta.warning = { error: errorMessage, @@ -111,14 +107,10 @@ class PendingTransactionTracker extends EventEmitter { const retryCount = txMeta.retryCount || 0 // Exponential backoff to limit retries at publishing - if (txBlockDistance <= Math.pow(2, retryCount) - 1) { - return - } + if (txBlockDistance <= Math.pow(2, retryCount) - 1) return // Only auto-submit already-signed txs: - if (!('rawTx' in txMeta)) { - return this.approveTransaction(txMeta.id) - } + if (!('rawTx' in txMeta)) return this.approveTransaction(txMeta.id) const rawTx = txMeta.rawTx const txHash = await this.publishTransaction(rawTx) @@ -140,9 +132,7 @@ class PendingTransactionTracker extends EventEmitter { const txId = txMeta.id // Only check submitted txs - if (txMeta.status !== 'submitted') { - return - } + if (txMeta.status !== 'submitted') return // extra check in case there was an uncaught error during the // signature and submission process diff --git a/app/scripts/controllers/transactions/tx-gas-utils.js b/app/scripts/controllers/transactions/tx-gas-utils.js index 517137f86..287fb6f44 100644 --- a/app/scripts/controllers/transactions/tx-gas-utils.js +++ b/app/scripts/controllers/transactions/tx-gas-utils.js @@ -142,13 +142,9 @@ class TxGasUtil { const bufferedGasLimitBn = initialGasLimitBn.muln(1.5) // if initialGasLimit is above blockGasLimit, dont modify it - if (initialGasLimitBn.gt(upperGasLimitBn)) { - return bnToHex(initialGasLimitBn) - } + if (initialGasLimitBn.gt(upperGasLimitBn)) return bnToHex(initialGasLimitBn) // if bufferedGasLimit is below blockGasLimit, use bufferedGasLimit - if (bufferedGasLimitBn.lt(upperGasLimitBn)) { - return bnToHex(bufferedGasLimitBn) - } + if (bufferedGasLimitBn.lt(upperGasLimitBn)) return bnToHex(bufferedGasLimitBn) // otherwise use blockGasLimit return bnToHex(upperGasLimitBn) } diff --git a/app/scripts/controllers/transactions/tx-state-manager.js b/app/scripts/controllers/transactions/tx-state-manager.js index ffbe6f010..6a92c0601 100644 --- a/app/scripts/controllers/transactions/tx-state-manager.js +++ b/app/scripts/controllers/transactions/tx-state-manager.js @@ -45,9 +45,7 @@ class TransactionStateManager extends EventEmitter { */ generateTxMeta (opts) { const netId = this.getNetwork() - if (netId === 'loading') { - throw new Error('MetaMask is having trouble connecting to the network') - } + if (netId === 'loading') throw new Error('MetaMask is having trouble connecting to the network') return extend({ id: createId(), time: (new Date()).getTime(), @@ -91,9 +89,7 @@ class TransactionStateManager extends EventEmitter { */ getApprovedTransactions (address) { const opts = { status: 'approved' } - if (address) { - opts.from = address - } + if (address) opts.from = address return this.getFilteredTxList(opts) } @@ -104,9 +100,7 @@ class TransactionStateManager extends EventEmitter { */ getPendingTransactions (address) { const opts = { status: 'submitted' } - if (address) { - opts.from = address - } + if (address) opts.from = address return this.getFilteredTxList(opts) } @@ -117,9 +111,7 @@ class TransactionStateManager extends EventEmitter { */ getConfirmedTransactions (address) { const opts = { status: 'confirmed' } - if (address) { - opts.from = address - } + if (address) opts.from = address return this.getFilteredTxList(opts) } @@ -167,12 +159,7 @@ class TransactionStateManager extends EventEmitter { transactions.splice(index, 1) } } - const newTxIndex = transactions - .findIndex((currentTxMeta) => currentTxMeta.time > txMeta.time) - - newTxIndex === -1 - ? transactions.push(txMeta) - : transactions.splice(newTxIndex, 0, txMeta) + transactions.push(txMeta) this._saveTxList(transactions) return txMeta } @@ -249,14 +236,10 @@ class TransactionStateManager extends EventEmitter { // validate types switch (key) { case 'chainId': - if (typeof value !== 'number' && typeof value !== 'string') { - throw new Error(`${key} in txParams is not a Number or hex string. got: (${value})`) - } + if (typeof value !== 'number' && typeof value !== 'string') throw new Error(`${key} in txParams is not a Number or hex string. got: (${value})`) break default: - if (typeof value !== 'string') { - throw new Error(`${key} in txParams is not a string. got: (${value})`) - } + if (typeof value !== 'string') throw new Error(`${key} in txParams is not a string. got: (${value})`) break } }) diff --git a/app/scripts/createStandardProvider.js b/app/scripts/createStandardProvider.js new file mode 100644 index 000000000..2059b9b3a --- /dev/null +++ b/app/scripts/createStandardProvider.js @@ -0,0 +1,73 @@ +class StandardProvider { + _isConnected + _provider + + constructor (provider) { + this._provider = provider + this._subscribe() + // indicate that we've connected, mostly just for standard compliance + setTimeout(() => { + this._onConnect() + }) + } + + _onClose () { + if (this._isConnected === undefined || this._isConnected) { + this._provider.emit('close', { + code: 1011, + reason: 'Network connection error', + }) + } + this._isConnected = false + } + + _onConnect () { + !this._isConnected && this._provider.emit('connect') + this._isConnected = true + } + + _subscribe () { + this._provider.on('data', (error, { method, params }) => { + if (!error && method === 'eth_subscription') { + this._provider.emit('notification', params.result) + } + }) + } + + /** + * Initiate an RPC method call + * + * @param {string} method - RPC method name to call + * @param {string[]} params - Array of RPC method parameters + * @returns {Promise<*>} Promise resolving to the result if successful + */ + send (method, params = []) { + return new Promise((resolve, reject) => { + try { + this._provider.sendAsync({ id: 1, jsonrpc: '2.0', method, params }, (error, response) => { + error = error || response.error + error ? reject(error) : resolve(response) + }) + } catch (error) { + reject(error) + } + }) + } +} + +/** + * Converts a legacy provider into an EIP-1193-compliant standard provider + * @param {Object} provider - Legacy provider to convert + * @returns {Object} Standard provider + */ +export default function createStandardProvider (provider) { + const standardProvider = new StandardProvider(provider) + const sendLegacy = provider.send + provider.send = (methodOrPayload, callbackOrArgs) => { + if (typeof methodOrPayload === 'string' && !callbackOrArgs || Array.isArray(callbackOrArgs)) { + return standardProvider.send(methodOrPayload, callbackOrArgs) + } + return sendLegacy.call(provider, methodOrPayload, callbackOrArgs) + } + return provider +} diff --git a/app/scripts/edge-encryptor.js b/app/scripts/edge-encryptor.js index d086a854d..012672ed2 100644 --- a/app/scripts/edge-encryptor.js +++ b/app/scripts/edge-encryptor.js @@ -14,17 +14,17 @@ class EdgeEncryptor { * @returns {Promise} Promise resolving to an object with ciphertext */ encrypt (password, dataObject) { - const salt = this._generateSalt() + var salt = this._generateSalt() return this._keyFromPassword(password, salt) .then(function (key) { - const data = JSON.stringify(dataObject) - const dataBuffer = Unibabel.utf8ToBuffer(data) - const vector = global.crypto.getRandomValues(new Uint8Array(16)) - const resultbuffer = asmcrypto.AES_GCM.encrypt(dataBuffer, key, vector) + var data = JSON.stringify(dataObject) + var dataBuffer = Unibabel.utf8ToBuffer(data) + var vector = global.crypto.getRandomValues(new Uint8Array(16)) + var resultbuffer = asmcrypto.AES_GCM.encrypt(dataBuffer, key, vector) - const buffer = new Uint8Array(resultbuffer) - const vectorStr = Unibabel.bufferToBase64(vector) - const vaultStr = Unibabel.bufferToBase64(buffer) + var buffer = new Uint8Array(resultbuffer) + var vectorStr = Unibabel.bufferToBase64(vector) + var vaultStr = Unibabel.bufferToBase64(buffer) return JSON.stringify({ data: vaultStr, iv: vectorStr, @@ -48,7 +48,7 @@ class EdgeEncryptor { const encryptedData = Unibabel.base64ToBuffer(payload.data) const vector = Unibabel.base64ToBuffer(payload.iv) return new Promise((resolve, reject) => { - let result + var result try { result = asmcrypto.AES_GCM.decrypt(encryptedData, key, vector) } catch (err) { @@ -72,12 +72,12 @@ class EdgeEncryptor { */ _keyFromPassword (password, salt) { - const passBuffer = Unibabel.utf8ToBuffer(password) - const saltBuffer = Unibabel.base64ToBuffer(salt) + var passBuffer = Unibabel.utf8ToBuffer(password) + var saltBuffer = Unibabel.base64ToBuffer(salt) const iterations = 10000 const length = 32 // SHA256 hash size return new Promise((resolve) => { - const key = asmcrypto.Pbkdf2HmacSha256(passBuffer, saltBuffer, iterations, length) + var key = asmcrypto.Pbkdf2HmacSha256(passBuffer, saltBuffer, iterations, length) resolve(key) }) } @@ -89,9 +89,9 @@ class EdgeEncryptor { * @returns {string} Randomized base64 encoded data */ _generateSalt (byteCount = 32) { - const view = new Uint8Array(byteCount) + var view = new Uint8Array(byteCount) global.crypto.getRandomValues(view) - const b64encoded = btoa(String.fromCharCode.apply(null, view)) + var b64encoded = btoa(String.fromCharCode.apply(null, view)) return b64encoded } } diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index d81d8587b..ec88243a4 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -1,5 +1,6 @@ /*global Web3*/ + // 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... @@ -31,14 +32,14 @@ const restoreContextAfterImports = () => { } cleanContextForImports() - +require('web3/dist/web3.min.js') const log = require('loglevel') const LocalMessageDuplexStream = require('post-message-stream') -const MetamaskInpageProvider = require('metamask-inpage-provider') - -// TODO:deprecate:2020-01-13 -require('web3/dist/web3.min.js') const setupDappAutoReload = require('./lib/auto-reload.js') +const MetamaskInpageProvider = require('metamask-inpage-provider') +const createStandardProvider = require('./createStandardProvider').default + +let warned = false restoreContextAfterImports() @@ -60,6 +61,89 @@ const inpageProvider = new MetamaskInpageProvider(metamaskStream) // set a high max listener count to avoid unnecesary warnings inpageProvider.setMaxListeners(100) +let warnedOfAutoRefreshDeprecation = false +// augment the provider with its enable method +inpageProvider.enable = function ({ force } = {}) { + if ( + !warnedOfAutoRefreshDeprecation && + inpageProvider.autoRefreshOnNetworkChange + ) { + console.warn(`MetaMask: MetaMask will soon stop reloading pages on network change. +If you rely upon this behavior, add a 'networkChanged' event handler to trigger the reload manually: https://metamask.github.io/metamask-docs/API_Reference/Ethereum_Provider#ethereum.on(eventname%2C-callback) +Set 'ethereum.autoRefreshOnNetworkChange' to 'false' to silence this warning: https://metamask.github.io/metamask-docs/API_Reference/Ethereum_Provider#ethereum.autorefreshonnetworkchange' +`) + warnedOfAutoRefreshDeprecation = true + } + return new Promise((resolve, reject) => { + inpageProvider.sendAsync({ method: 'eth_requestAccounts', params: [force] }, (error, response) => { + if (error || response.error) { + reject(error || response.error) + } else { + resolve(response.result) + } + }) + }) +} + +// give the dapps control of a refresh they can toggle this off on the window.ethereum +// this will be default true so it does not break any old apps. +inpageProvider.autoRefreshOnNetworkChange = true + + +// publicConfig isn't populated until we get a message from background. +// Using this getter will ensure the state is available +const getPublicConfigWhenReady = async () => { + 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 +} + +// add metamask-specific convenience methods +inpageProvider._metamask = new Proxy({ + /** + * Synchronously determines if this domain is currently enabled, with a potential false negative if called to soon + * + * @returns {boolean} - returns true if this domain is currently enabled + */ + isEnabled: function () { + const { isEnabled } = inpageProvider.publicConfigStore.getState() + return Boolean(isEnabled) + }, + + /** + * Asynchronously determines if this domain is currently enabled + * + * @returns {Promise} - Promise resolving to true if this domain is currently enabled + */ + isApproved: async function () { + const { isEnabled } = await getPublicConfigWhenReady() + return Boolean(isEnabled) + }, + + /** + * Determines if MetaMask is unlocked by the user + * + * @returns {Promise} - Promise resolving to true if MetaMask is currently unlocked + */ + isUnlocked: async function () { + const { isUnlocked } = await getPublicConfigWhenReady() + return Boolean(isUnlocked) + }, +}, { + get: function (obj, prop) { + !warned && console.warn('Heads up! ethereum._metamask exposes methods that have ' + + 'not been standardized yet. This means that these methods may not be implemented ' + + 'in other dapp browsers and may be removed from MetaMask in the future.') + warned = true + return obj[prop] + }, +}) + // Work around for web3@1.0 deleting the bound `sendAsync` but not the unbound // `sendAsync` method on the prototype, causing `this` reference issues const proxiedInpageProvider = new Proxy(inpageProvider, { @@ -68,11 +152,11 @@ const proxiedInpageProvider = new Proxy(inpageProvider, { deleteProperty: () => true, }) -// -// TODO:deprecate:2020-01-13 -// +window.ethereum = createStandardProvider(proxiedInpageProvider) +// // setup web3 +// if (typeof window.web3 !== 'undefined') { throw new Error(`MetaMask detected another web3. @@ -88,13 +172,15 @@ web3.setProvider = function () { } log.debug('MetaMask - injected web3') -proxiedInpageProvider._web3Ref = web3.eth +setupDappAutoReload(web3, inpageProvider.publicConfigStore) -// setup dapp auto reload AND proxy web3 -setupDappAutoReload(web3, inpageProvider._publicConfigStore) +// set web3 defaultAccount +inpageProvider.publicConfigStore.subscribe(function (state) { + web3.eth.defaultAccount = state.selectedAddress +}) -// -// end deprecate:2020-01-13 -// - -window.ethereum = proxiedInpageProvider +inpageProvider.publicConfigStore.subscribe(function (state) { + if (state.onboardingcomplete) { + window.postMessage('onboardingcomplete', '*') + } +}) diff --git a/app/scripts/lib/account-tracker.js b/app/scripts/lib/account-tracker.js index 8fef712fe..3137ea8a3 100644 --- a/app/scripts/lib/account-tracker.js +++ b/app/scripts/lib/account-tracker.js @@ -124,9 +124,7 @@ class AccountTracker { // save accounts state this.store.updateState({ accounts }) // fetch balances for the accounts if there is block number ready - if (!this._currentBlockNumber) { - return - } + if (!this._currentBlockNumber) return this._updateAccounts() } @@ -160,9 +158,7 @@ class AccountTracker { // block gasLimit polling shouldn't be in account-tracker shouldn't be here... const currentBlock = await this._query.getBlockByNumber(blockNumber, false) - if (!currentBlock) { - return - } + if (!currentBlock) return const currentBlockGasLimit = currentBlock.gasLimit this.store.updateState({ currentBlockGasLimit }) @@ -222,9 +218,7 @@ class AccountTracker { // update accounts state const { accounts } = this.store.getState() // only populate if the entry is still present - if (!accounts[address]) { - return - } + if (!accounts[address]) return accounts[address] = result this.store.updateState({ accounts }) } diff --git a/app/scripts/lib/auto-reload.js b/app/scripts/lib/auto-reload.js index e2b9c17bd..fd209c230 100644 --- a/app/scripts/lib/auto-reload.js +++ b/app/scripts/lib/auto-reload.js @@ -1,6 +1,3 @@ - -// TODO:deprecate:2020-01-13 - module.exports = setupDappAutoReload function setupDappAutoReload (web3, observable) { @@ -16,7 +13,7 @@ function setupDappAutoReload (web3, observable) { lastTimeUsed = Date.now() // show warning once on web3 access if (!hasBeenWarned && key !== 'currentProvider') { - console.warn(`MetaMask: On 2020-01-13, MetaMask will no longer inject web3. For more information, see: https://medium.com/metamask/no-longer-injecting-web3-js-4a899ad6e59e`) + console.warn('MetaMask: web3 will be deprecated in the near future in favor of the ethereumProvider\nhttps://medium.com/metamask/4a899ad6e59e') hasBeenWarned = true } // return value normally @@ -31,14 +28,10 @@ function setupDappAutoReload (web3, observable) { observable.subscribe(function (state) { // if the auto refresh on network change is false do not // do anything - if (!window.ethereum.autoRefreshOnNetworkChange) { - return - } + if (!window.ethereum.autoRefreshOnNetworkChange) return // if reload in progress, no need to check reload logic - if (reloadInProgress) { - return - } + if (reloadInProgress) return const currentNetwork = state.networkVersion @@ -49,14 +42,10 @@ function setupDappAutoReload (web3, observable) { } // skip reload logic if web3 not used - if (!lastTimeUsed) { - return - } + if (!lastTimeUsed) return // if network did not change, exit - if (currentNetwork === lastSeenNetwork) { - return - } + if (currentNetwork === lastSeenNetwork) return // initiate page reload reloadInProgress = true diff --git a/app/scripts/lib/buy-eth-url.js b/app/scripts/lib/buy-eth-url.js index 6892364ca..5cae83a9f 100644 --- a/app/scripts/lib/buy-eth-url.js +++ b/app/scripts/lib/buy-eth-url.js @@ -13,13 +13,11 @@ module.exports = getBuyEthUrl */ function getBuyEthUrl ({ network, amount, address, service }) { // default service by network if not specified - if (!service) { - service = getDefaultServiceForNetwork(network) - } + if (!service) service = getDefaultServiceForNetwork(network) switch (service) { case 'wyre': - return `https://pay.sendwyre.com/?dest=ethereum:${address}&destCurrency=ETH&accountId=AC-7AG3W4XH4N2` + return `https://dash.sendwyre.com/sign-up` case 'coinswitch': return `https://metamask.coinswitch.co/?address=${address}&to=eth` case 'coinbase': @@ -32,9 +30,8 @@ function getBuyEthUrl ({ network, amount, address, service }) { return 'https://github.com/kovan-testnet/faucet' case 'goerli-faucet': return 'https://goerli-faucet.slock.it/' - default: - throw new Error(`Unknown cryptocurrency exchange or faucet: "${service}"`) } + throw new Error(`Unknown cryptocurrency exchange or faucet: "${service}"`) } function getDefaultServiceForNetwork (network) { @@ -49,7 +46,6 @@ function getDefaultServiceForNetwork (network) { return 'kovan-faucet' case '5': return 'goerli-faucet' - default: - throw new Error(`No default cryptocurrency exchange or faucet for networkId: "${network}"`) } + throw new Error(`No default cryptocurrency exchange or faucet for networkId: "${network}"`) } diff --git a/app/scripts/lib/cleanErrorStack.js b/app/scripts/lib/cleanErrorStack.js index 58f7ccfdf..8adf55db7 100644 --- a/app/scripts/lib/cleanErrorStack.js +++ b/app/scripts/lib/cleanErrorStack.js @@ -4,10 +4,10 @@ * @returns {Error} Error with clean stack trace. */ function cleanErrorStack (err) { - let name = err.name + var name = err.name name = (name === undefined) ? 'Error' : String(name) - let msg = err.message + var msg = err.message msg = (msg === undefined) ? '' : String(msg) if (name === '') { diff --git a/app/scripts/lib/createDnodeRemoteGetter.js b/app/scripts/lib/createDnodeRemoteGetter.js index 60a775383..b70d218f3 100644 --- a/app/scripts/lib/createDnodeRemoteGetter.js +++ b/app/scripts/lib/createDnodeRemoteGetter.js @@ -8,9 +8,7 @@ function createDnodeRemoteGetter (dnode) { }) async function getRemote () { - if (remote) { - return remote - } + if (remote) return remote return await new Promise(resolve => dnode.once('remote', resolve)) } diff --git a/app/scripts/lib/createLoggerMiddleware.js b/app/scripts/lib/createLoggerMiddleware.js index d95cdb465..996c3477c 100644 --- a/app/scripts/lib/createLoggerMiddleware.js +++ b/app/scripts/lib/createLoggerMiddleware.js @@ -13,9 +13,7 @@ function createLoggerMiddleware (opts) { if (res.error) { log.error('Error in RPC response:\n', res) } - if (req.isMetamaskInternal) { - return - } + if (req.isMetamaskInternal) return log.info(`RPC (${opts.origin}):`, req, '->', res) cb() }) diff --git a/app/scripts/lib/createOriginMiddleware.js b/app/scripts/lib/createOriginMiddleware.js index 4ff9a3386..98bb0e3b3 100644 --- a/app/scripts/lib/createOriginMiddleware.js +++ b/app/scripts/lib/createOriginMiddleware.js @@ -1,4 +1,3 @@ - module.exports = createOriginMiddleware /** diff --git a/app/scripts/lib/ens-ipfs/resolver.js b/app/scripts/lib/ens-ipfs/resolver.js index 34b1304c1..a0af263bc 100644 --- a/app/scripts/lib/ens-ipfs/resolver.js +++ b/app/scripts/lib/ens-ipfs/resolver.js @@ -66,7 +66,5 @@ function getRegistryForChainId (chainId) { // goerli case 5: return '0x112234455c3a32fd11230c42e7bccd4a84e02010' - default: - return null } } diff --git a/app/scripts/lib/ens-ipfs/setup.js b/app/scripts/lib/ens-ipfs/setup.js index f12a22238..a3711c5f9 100644 --- a/app/scripts/lib/ens-ipfs/setup.js +++ b/app/scripts/lib/ens-ipfs/setup.js @@ -23,18 +23,14 @@ function setupEnsIpfsResolver ({ provider }) { async function webRequestDidFail (details) { const { tabId, url } = details // ignore requests that are not associated with tabs - if (tabId === -1) { - return - } + if (tabId === -1) return // parse ens name const urlData = urlUtil.parse(url) const { hostname: name, path, search } = urlData const domainParts = name.split('.') const topLevelDomain = domainParts[domainParts.length - 1] // if unsupported TLD, abort - if (!supportedTopLevelDomains.includes(topLevelDomain)) { - return - } + if (!supportedTopLevelDomains.includes(topLevelDomain)) return // otherwise attempt resolve attemptResolve({ tabId, name, path, search }) } @@ -49,9 +45,7 @@ function setupEnsIpfsResolver ({ provider }) { try { // check if ipfs gateway has result const response = await fetch(resolvedUrl, { method: 'HEAD' }) - if (response.status === 200) { - url = resolvedUrl - } + if (response.status === 200) url = resolvedUrl } catch (err) { console.warn(err) } diff --git a/app/scripts/lib/local-store.js b/app/scripts/lib/local-store.js index b7212c980..8fde2e911 100644 --- a/app/scripts/lib/local-store.js +++ b/app/scripts/lib/local-store.js @@ -1,6 +1,5 @@ const extension = require('extensionizer') const log = require('loglevel') -const { checkForError } = require('./util') /** * A wrapper around the extension's storage local API @@ -21,9 +20,7 @@ module.exports = class ExtensionStore { * @return {Promise<*>} */ async get () { - if (!this.isSupported) { - return undefined - } + if (!this.isSupported) return undefined const result = await this._get() // extension.storage.local always returns an obj // if the object is empty, treat it as undefined @@ -91,3 +88,17 @@ module.exports = class ExtensionStore { function isEmpty (obj) { return Object.keys(obj).length === 0 } + +/** + * Returns an Error if extension.runtime.lastError is present + * this is a workaround for the non-standard error object thats used + * @returns {Error} + */ +function checkForError () { + const lastError = extension.runtime.lastError + if (!lastError) return + // if it quacks like an Error, its an Error + if (lastError.stack && lastError.message) return lastError + // repair incomplete error object (eg chromium v77) + return new Error(lastError.message) +} diff --git a/app/scripts/lib/message-manager.js b/app/scripts/lib/message-manager.js index a947188e5..8e1ff34b7 100644 --- a/app/scripts/lib/message-manager.js +++ b/app/scripts/lib/message-manager.js @@ -1,7 +1,7 @@ const EventEmitter = require('events') const ObservableStore = require('obs-store') const ethUtil = require('ethereumjs-util') -const { ethErrors } = require('eth-json-rpc-errors') +const { errors: rpcErrors } = require('eth-json-rpc-errors') const createId = require('./random-id') /** @@ -62,9 +62,7 @@ module.exports = class MessageManager extends EventEmitter { */ getUnapprovedMsgs () { return this.messages.filter(msg => msg.status === 'unapproved') - .reduce((result, msg) => { - result[msg.id] = msg; return result - }, {}) + .reduce((result, msg) => { result[msg.id] = msg; return result }, {}) } /** @@ -85,7 +83,7 @@ module.exports = class MessageManager extends EventEmitter { case 'signed': return resolve(data.rawSig) case 'rejected': - return reject(ethErrors.provider.userRejectedRequest('MetaMask Message Signature: User denied message signature.')) + return reject(rpcErrors.eth.userRejectedRequest('MetaMask Message Signature: User denied message signature.')) default: return reject(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)) } @@ -104,14 +102,12 @@ module.exports = class MessageManager extends EventEmitter { */ addUnapprovedMessage (msgParams, req) { // add origin from request - if (req) { - msgParams.origin = req.origin - } + if (req) msgParams.origin = req.origin msgParams.data = normalizeMsgData(msgParams.data) // create txData obj with parameters and meta data - const time = (new Date()).getTime() - const msgId = createId() - const msgData = { + var time = (new Date()).getTime() + var msgId = createId() + var msgData = { id: msgId, msgParams: msgParams, time: time, @@ -223,9 +219,7 @@ module.exports = class MessageManager extends EventEmitter { */ _setMsgStatus (msgId, status) { const msg = this.getMsg(msgId) - if (!msg) { - throw new Error('MessageManager - Message not found for id: "${msgId}".') - } + if (!msg) throw new Error('MessageManager - Message not found for id: "${msgId}".') msg.status = status this._updateMsg(msg) this.emit(`${msgId}:${status}`, msg) diff --git a/app/scripts/lib/migrator/index.js b/app/scripts/lib/migrator/index.js index c1c225fb3..345ca8001 100644 --- a/app/scripts/lib/migrator/index.js +++ b/app/scripts/lib/migrator/index.js @@ -40,12 +40,8 @@ class Migrator extends EventEmitter { try { // attempt migration and validate const migratedData = await migration.migrate(versionedData) - if (!migratedData.data) { - throw new Error('Migrator - migration returned empty data') - } - if (migratedData.version !== undefined && migratedData.meta.version !== migration.version) { - throw new Error('Migrator - Migration did not update version number correctly') - } + if (!migratedData.data) throw new Error('Migrator - migration returned empty data') + if (migratedData.version !== undefined && migratedData.meta.version !== migration.version) throw new Error('Migrator - Migration did not update version number correctly') // accept the migration as good versionedData = migratedData } catch (err) { diff --git a/app/scripts/lib/nodeify.js b/app/scripts/lib/nodeify.js index 99b96b356..a813ae679 100644 --- a/app/scripts/lib/nodeify.js +++ b/app/scripts/lib/nodeify.js @@ -1,9 +1,5 @@ const promiseToCallback = require('promise-to-callback') -const callbackNoop = function (err) { - if (err) { - throw err - } -} +const callbackNoop = function (err) { if (err) throw err } /** * A generator that returns a function which, when passed a promise, can treat that promise as a node style callback. diff --git a/app/scripts/lib/notification-manager.js b/app/scripts/lib/notification-manager.js index 85177cceb..721d109a1 100644 --- a/app/scripts/lib/notification-manager.js +++ b/app/scripts/lib/notification-manager.js @@ -18,9 +18,7 @@ class NotificationManager { */ showPopup () { this._getPopup((err, popup) => { - if (err) { - throw err - } + if (err) throw err // Bring focus to chrome popup if (popup) { @@ -30,9 +28,7 @@ class NotificationManager { const {screenX, screenY, outerWidth, outerHeight} = window const notificationTop = Math.round(screenY + (outerHeight / 2) - (NOTIFICATION_HEIGHT / 2)) const notificationLeft = Math.round(screenX + (outerWidth / 2) - (NOTIFICATION_WIDTH / 2)) - const cb = (currentPopup) => { - this._popupId = currentPopup.id - } + const cb = (currentPopup) => { this._popupId = currentPopup.id } // create new notification popup const creation = extension.windows.create({ url: 'notification.html', @@ -54,12 +50,8 @@ class NotificationManager { closePopup () { // closes notification popup this._getPopup((err, popup) => { - if (err) { - throw err - } - if (!popup) { - return - } + if (err) throw err + if (!popup) return extension.windows.remove(popup.id, console.error) }) } @@ -74,9 +66,7 @@ class NotificationManager { */ _getPopup (cb) { this._getWindows((err, windows) => { - if (err) { - throw err - } + if (err) throw err cb(null, this._getPopupIn(windows)) }) } diff --git a/app/scripts/lib/pending-balance-calculator.js b/app/scripts/lib/pending-balance-calculator.js index dd7fa6de4..0f1dc19a9 100644 --- a/app/scripts/lib/pending-balance-calculator.js +++ b/app/scripts/lib/pending-balance-calculator.js @@ -32,9 +32,7 @@ class PendingBalanceCalculator { ]) const [ balance, pending ] = results - if (!balance) { - return undefined - } + if (!balance) return undefined const pendingValue = pending.reduce((total, tx) => { return total.add(this.calculateMaxCost(tx)) diff --git a/app/scripts/lib/personal-message-manager.js b/app/scripts/lib/personal-message-manager.js index fb30cc03c..2a2ab481a 100644 --- a/app/scripts/lib/personal-message-manager.js +++ b/app/scripts/lib/personal-message-manager.js @@ -1,7 +1,7 @@ const EventEmitter = require('events') const ObservableStore = require('obs-store') const ethUtil = require('ethereumjs-util') -const { ethErrors } = require('eth-json-rpc-errors') +const { errors: rpcErrors } = require('eth-json-rpc-errors') const createId = require('./random-id') const hexRe = /^[0-9A-Fa-f]+$/g const log = require('loglevel') @@ -65,9 +65,7 @@ module.exports = class PersonalMessageManager extends EventEmitter { */ getUnapprovedMsgs () { return this.messages.filter(msg => msg.status === 'unapproved') - .reduce((result, msg) => { - result[msg.id] = msg; return result - }, {}) + .reduce((result, msg) => { result[msg.id] = msg; return result }, {}) } /** @@ -91,7 +89,7 @@ module.exports = class PersonalMessageManager extends EventEmitter { case 'signed': return resolve(data.rawSig) case 'rejected': - return reject(ethErrors.provider.userRejectedRequest('MetaMask Message Signature: User denied message signature.')) + return reject(rpcErrors.eth.userRejectedRequest('MetaMask Message Signature: User denied message signature.')) default: return reject(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)) } @@ -112,14 +110,12 @@ module.exports = class PersonalMessageManager extends EventEmitter { addUnapprovedMessage (msgParams, req) { log.debug(`PersonalMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`) // add origin from request - if (req) { - msgParams.origin = req.origin - } + if (req) msgParams.origin = req.origin msgParams.data = this.normalizeMsgData(msgParams.data) // create txData obj with parameters and meta data - const time = (new Date()).getTime() - const msgId = createId() - const msgData = { + var time = (new Date()).getTime() + var msgId = createId() + var msgData = { id: msgId, msgParams: msgParams, time: time, @@ -233,9 +229,7 @@ module.exports = class PersonalMessageManager extends EventEmitter { */ _setMsgStatus (msgId, status) { const msg = this.getMsg(msgId) - if (!msg) { - throw new Error(`PersonalMessageManager - Message not found for id: "${msgId}".`) - } + if (!msg) throw new Error(`PersonalMessageManager - Message not found for id: "${msgId}".`) msg.status = status this._updateMsg(msg) this.emit(`${msgId}:${status}`, msg) diff --git a/app/scripts/lib/select-chain-id.js b/app/scripts/lib/select-chain-id.js index d94b35898..3171c9840 100644 --- a/app/scripts/lib/select-chain-id.js +++ b/app/scripts/lib/select-chain-id.js @@ -15,8 +15,8 @@ const standardNetworkId = { } function selectChainId (metamaskState) { - const { network, provider: { chainId } } = metamaskState - return standardNetworkId[network] || `0x${parseInt(chainId, 10).toString(16)}` + const { network, provider: { chaindId } } = metamaskState + return standardNetworkId[network] || `0x${parseInt(chaindId, 10).toString(16)}` } module.exports = selectChainId diff --git a/app/scripts/lib/setupFetchDebugging.js b/app/scripts/lib/setupFetchDebugging.js index d5c01b1f2..431340e2b 100644 --- a/app/scripts/lib/setupFetchDebugging.js +++ b/app/scripts/lib/setupFetchDebugging.js @@ -7,9 +7,7 @@ module.exports = setupFetchDebugging // function setupFetchDebugging () { - if (!global.fetch) { - return - } + if (!global.fetch) return const originalFetch = global.fetch global.fetch = wrappedFetch diff --git a/app/scripts/lib/setupMetamaskMeshMetrics.js b/app/scripts/lib/setupMetamaskMeshMetrics.js index b520ceaa7..6f0d86b28 100644 --- a/app/scripts/lib/setupMetamaskMeshMetrics.js +++ b/app/scripts/lib/setupMetamaskMeshMetrics.js @@ -22,9 +22,7 @@ function setupMetamaskMeshMetrics () { function submitMeshMetricsEntry (message) { // ignore if we haven't loaded yet - if (!didLoad) { - return - } + if (!didLoad) return // submit the message testingContainer.contentWindow.postMessage(message, targetOrigin) } diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js index 096bd3454..ba0e17df0 100644 --- a/app/scripts/lib/setupSentry.js +++ b/app/scripts/lib/setupSentry.js @@ -67,15 +67,11 @@ function simplifyErrorMessages (report) { function rewriteErrorMessages (report, rewriteFn) { // rewrite top level message - if (typeof report.message === 'string') { - report.message = rewriteFn(report.message) - } + if (typeof report.message === 'string') report.message = rewriteFn(report.message) // rewrite each exception message if (report.exception && report.exception.values) { report.exception.values.forEach(item => { - if (typeof item.value === 'string') { - item.value = rewriteFn(item.value) - } + if (typeof item.value === 'string') item.value = rewriteFn(item.value) }) } } @@ -95,9 +91,7 @@ function rewriteReportUrls (report) { function toMetamaskUrl (origUrl) { const filePath = origUrl.split(location.origin)[1] - if (!filePath) { - return origUrl - } + if (!filePath) return origUrl const metamaskUrl = `metamask${filePath}` return metamaskUrl } diff --git a/app/scripts/lib/stream-utils.js b/app/scripts/lib/stream-utils.js index 66b3107f4..3dbc064b5 100644 --- a/app/scripts/lib/stream-utils.js +++ b/app/scripts/lib/stream-utils.js @@ -43,9 +43,7 @@ function setupMultiplex (connectionStream) { mux, connectionStream, (err) => { - if (err) { - console.error(err) - } + if (err) console.error(err) } ) return mux diff --git a/app/scripts/lib/typed-message-manager.js b/app/scripts/lib/typed-message-manager.js index 5e3c48038..e4d3e842b 100644 --- a/app/scripts/lib/typed-message-manager.js +++ b/app/scripts/lib/typed-message-manager.js @@ -2,7 +2,7 @@ const EventEmitter = require('events') const ObservableStore = require('obs-store') const createId = require('./random-id') const assert = require('assert') -const { ethErrors } = require('eth-json-rpc-errors') +const { errors: rpcErrors } = require('eth-json-rpc-errors') const sigUtil = require('eth-sig-util') const log = require('loglevel') const jsonschema = require('jsonschema') @@ -58,9 +58,7 @@ module.exports = class TypedMessageManager extends EventEmitter { */ getUnapprovedMsgs () { return this.messages.filter(msg => msg.status === 'unapproved') - .reduce((result, msg) => { - result[msg.id] = msg; return result - }, {}) + .reduce((result, msg) => { result[msg.id] = msg; return result }, {}) } /** @@ -81,7 +79,7 @@ module.exports = class TypedMessageManager extends EventEmitter { case 'signed': return resolve(data.rawSig) case 'rejected': - return reject(ethErrors.provider.userRejectedRequest('MetaMask Message Signature: User denied message signature.')) + return reject(rpcErrors.eth.userRejectedRequest('MetaMask Message Signature: User denied message signature.')) case 'errored': return reject(new Error(`MetaMask Message Signature: ${data.error}`)) default: @@ -105,15 +103,13 @@ module.exports = class TypedMessageManager extends EventEmitter { msgParams.version = version this.validateParams(msgParams) // add origin from request - if (req) { - msgParams.origin = req.origin - } + if (req) msgParams.origin = req.origin log.debug(`TypedMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`) // create txData obj with parameters and meta data - const time = (new Date()).getTime() - const msgId = createId() - const msgData = { + var time = (new Date()).getTime() + var msgId = createId() + var msgData = { id: msgId, msgParams: msgParams, time: time, @@ -153,9 +149,7 @@ module.exports = class TypedMessageManager extends EventEmitter { assert.ok('from' in params, 'Params must include a from field.') assert.equal(typeof params.from, 'string', 'From field must be a string.') assert.equal(typeof params.data, 'string', 'Data must be passed as a valid JSON string.') - assert.doesNotThrow(() => { - data = JSON.parse(params.data) - }, 'Data must be passed as a valid JSON string.') + assert.doesNotThrow(() => { data = JSON.parse(params.data) }, 'Data must be passed as a valid JSON string.') const validation = jsonschema.validate(data, sigUtil.TYPED_MESSAGE_SCHEMA) assert.ok(data.primaryType in data.types, `Primary type of "${data.primaryType}" has no type definition.`) assert.equal(validation.errors.length, 0, 'Data must conform to EIP-712 schema. See https://git.io/fNtcx.') @@ -163,8 +157,6 @@ module.exports = class TypedMessageManager extends EventEmitter { const activeChainId = parseInt(this.networkController.getNetworkState()) chainId && assert.equal(chainId, activeChainId, `Provided chainId (${chainId}) must match the active chainId (${activeChainId})`) break - default: - assert.fail(`Unknown params.version ${params.version}`) } } @@ -286,9 +278,7 @@ module.exports = class TypedMessageManager extends EventEmitter { */ _setMsgStatus (msgId, status) { const msg = this.getMsg(msgId) - if (!msg) { - throw new Error('TypedMessageManager - Message not found for id: "${msgId}".') - } + if (!msg) throw new Error('TypedMessageManager - Message not found for id: "${msgId}".') msg.status = status this._updateMsg(msg) this.emit(`${msgId}:${status}`, msg) diff --git a/app/scripts/lib/util.js b/app/scripts/lib/util.js index 36b836eb1..114203d7f 100644 --- a/app/scripts/lib/util.js +++ b/app/scripts/lib/util.js @@ -1,4 +1,3 @@ -const extension = require('extensionizer') const ethUtil = require('ethereumjs-util') const assert = require('assert') const BN = require('bn.js') @@ -149,32 +148,6 @@ function getRandomArrayItem (array) { return array[Math.floor((Math.random() * array.length))] } -function mapObjectValues (object, cb) { - const mappedObject = {} - Object.keys(object).forEach(key => { - mappedObject[key] = cb(key, object[key]) - }) - return mappedObject -} - -/** - * Returns an Error if extension.runtime.lastError is present - * this is a workaround for the non-standard error object thats used - * @returns {Error} - */ -function checkForError () { - const lastError = extension.runtime.lastError - if (!lastError) { - return - } - // if it quacks like an Error, its an Error - if (lastError.stack && lastError.message) { - return lastError - } - // repair incomplete error object (eg chromium v77) - return new Error(lastError.message) -} - module.exports = { removeListeners, applyListeners, @@ -186,6 +159,4 @@ module.exports = { bnToHex, BnMultiplyByFraction, getRandomArrayItem, - mapObjectValues, - checkForError, } diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 246ae511d..14caf0706 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -4,13 +4,13 @@ * @license MIT */ -const assert = require('assert').strict const EventEmitter = require('events') const pump = require('pump') const Dnode = require('dnode') -const extension = require('extensionizer') +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,8 +18,8 @@ const debounce = require('debounce') const createEngineStream = require('json-rpc-middleware-stream/engineStream') const createFilterMiddleware = require('eth-json-rpc-filters') const createSubscriptionManager = require('eth-json-rpc-filters/subscriptionManager') -const createLoggerMiddleware = require('./lib/createLoggerMiddleware') const createOriginMiddleware = require('./lib/createOriginMiddleware') +const createLoggerMiddleware = require('./lib/createLoggerMiddleware') const providerAsMiddleware = require('eth-json-rpc-middleware/providerAsMiddleware') const {setupMultiplex} = require('./lib/stream-utils.js') const KeyringController = require('eth-keyring-controller') @@ -39,8 +39,8 @@ const TypedMessageManager = require('./lib/typed-message-manager') const TransactionController = require('./controllers/transactions') const TokenRatesController = require('./controllers/token-rates') const DetectTokensController = require('./controllers/detect-tokens') +const ProviderApprovalController = require('./controllers/provider-approval') const ABTestController = require('./controllers/ab-test') -const { PermissionsController } = require('./controllers/permissions/') const nodeify = require('./lib/nodeify') const accountImporter = require('./account-import-strategies') const getBuyEthUrl = require('./lib/buy-eth-url') @@ -56,7 +56,6 @@ const TrezorKeyring = require('eth-trezor-keyring') const LedgerBridgeKeyring = require('eth-ledger-bridge-keyring') const EthQuery = require('eth-query') const ethUtil = require('ethereumjs-util') -const nanoid = require('nanoid') const contractMap = require('eth-contract-metadata') const { AddressBookController, @@ -89,24 +88,16 @@ module.exports = class MetamaskController extends EventEmitter { // platform-specific api this.platform = opts.platform - this.getRequestAccountTabIds = opts.getRequestAccountTabIds - this.getOpenMetamaskTabsIds = opts.getOpenMetamaskTabsIds - // observable state store this.store = new ComposableObservableStore(initState) - // external connections by origin - // Do not modify directly. Use the associated methods. - this.connections = {} - // lock to ensure only one vault created at once this.createVaultMutex = new Mutex() - // next, we will initialize the controllers - // controller initializaiton order matters - + // network store this.networkController = new NetworkController(initState.NetworkController) + // preferences controller this.preferencesController = new PreferencesController({ initState: initState.PreferencesController, initLangCode: opts.initLangCode, @@ -114,14 +105,16 @@ module.exports = class MetamaskController extends EventEmitter { network: this.networkController, }) + // app-state controller this.appStateController = new AppStateController({ preferencesStore: this.preferencesController.store, onInactiveTimeout: () => this.setLocked(), - initState: initState.AppStateController, }) + // currency controller this.currencyRateController = new CurrencyRateController(undefined, initState.CurrencyController) + // infura controller this.infuraController = new InfuraController({ initState: initState.InfuraController, }) @@ -129,7 +122,7 @@ module.exports = class MetamaskController extends EventEmitter { this.phishingController = new PhishingController() - // now we can initialize the RPC provider, which other controllers require + // rpc provider this.initializeProvider() this.provider = this.networkController.getProviderAndBlockTracker().provider this.blockTracker = this.networkController.getProviderAndBlockTracker().blockTracker @@ -158,7 +151,7 @@ module.exports = class MetamaskController extends EventEmitter { initState: initState.IncomingTransactionsController, }) - // account tracker watches balances, nonces, and any code at their address + // account tracker watches balances, nonces, and any code at their address. this.accountTracker = new AccountTracker({ provider: this.provider, blockTracker: this.blockTracker, @@ -184,7 +177,6 @@ module.exports = class MetamaskController extends EventEmitter { this.onboardingController = new OnboardingController({ initState: initState.OnboardingController, - preferencesController: this.preferencesController, }) // ensure accountTracker updates balances after network change @@ -192,6 +184,7 @@ module.exports = class MetamaskController extends EventEmitter { this.accountTracker._updateAccounts() }) + // key mgmt const additionalKeyrings = [TrezorKeyring, LedgerBridgeKeyring] this.keyringController = new KeyringController({ keyringTypes: additionalKeyrings, @@ -199,25 +192,16 @@ module.exports = class MetamaskController extends EventEmitter { getNetwork: this.networkController.getNetworkState.bind(this.networkController), encryptor: opts.encryptor || undefined, }) + this.keyringController.memStore.subscribe((s) => this._onKeyringControllerUpdate(s)) - this.permissionsController = new PermissionsController({ - keyringController: this.keyringController, - platform: opts.platform, - notifyDomain: this.notifyConnections.bind(this), - notifyAllDomains: this.notifyAllConnections.bind(this), - }, initState.PermissionsController, initState.PermissionsMetadata) - + // detect tokens controller this.detectTokensController = new DetectTokensController({ preferences: this.preferencesController, network: this.networkController, keyringMemStore: this.keyringController.memStore, }) - this.abTestController = new ABTestController({ - initState: initState.ABTestController, - }) - this.addressBookController = new AddressBookController(undefined, initState.AddressBookController) this.threeBoxController = new ThreeBoxController({ @@ -229,9 +213,9 @@ module.exports = class MetamaskController extends EventEmitter { version, }) + // tx mgmt this.txController = new TransactionController({ initState: initState.TransactionController || initState.TransactionManager, - getPermittedAccounts: this.permissionsController.getAccounts.bind(this.permissionsController), networkStore: this.networkController.networkStore, preferencesStore: this.preferencesController.store, txHistoryLimit: 40, @@ -282,6 +266,18 @@ module.exports = class MetamaskController extends EventEmitter { this.isClientOpenAndUnlocked = memState.isUnlocked && this._isClientOpen }) + this.providerApprovalController = new ProviderApprovalController({ + closePopup: opts.closePopup, + initState: initState.ProviderApprovalController, + keyringController: this.keyringController, + openPopup: opts.openPopup, + preferencesController: this.preferencesController, + }) + + this.abTestController = new ABTestController({ + initState: initState.ABTestController, + }) + this.store.updateStructure({ AppStateController: this.appStateController.store, TransactionController: this.txController.store, @@ -294,11 +290,10 @@ module.exports = class MetamaskController extends EventEmitter { InfuraController: this.infuraController.store, CachedBalancesController: this.cachedBalancesController.store, OnboardingController: this.onboardingController.store, + ProviderApprovalController: this.providerApprovalController.store, IncomingTransactionsController: this.incomingTransactionsController.store, - ABTestController: this.abTestController.store, - PermissionsController: this.permissionsController.permissions, - PermissionsMetadata: this.permissionsController.store, ThreeBoxController: this.threeBoxController.store, + ABTestController: this.abTestController.store, }) this.memStore = new ComposableObservableStore(null, { @@ -319,9 +314,11 @@ module.exports = class MetamaskController extends EventEmitter { ShapeshiftController: this.shapeshiftController, InfuraController: this.infuraController.store, OnboardingController: this.onboardingController.store, + // ProviderApprovalController + ProviderApprovalController: this.providerApprovalController.store, + ProviderApprovalControllerMemStore: this.providerApprovalController.memStore, IncomingTransactionsController: this.incomingTransactionsController.store, - PermissionsController: this.permissionsController.permissions, - PermissionsMetadata: this.permissionsController.store, + // ThreeBoxController ThreeBoxController: this.threeBoxController.store, ABTestController: this.abTestController.store, // ENS Controller @@ -342,15 +339,18 @@ module.exports = class MetamaskController extends EventEmitter { version, // account mgmt getAccounts: async ({ origin }) => { - if (origin === 'metamask') { - const selectedAddress = this.preferencesController.getSelectedAddress() - return selectedAddress ? [selectedAddress] : [] - } else if ( - this.keyringController.memStore.getState().isUnlocked - ) { - return await this.permissionsController.getAccounts(origin) + // Expose no accounts if this origin has not been approved, preventing + // account-requring RPC methods from completing successfully + const exposeAccounts = this.providerApprovalController.shouldExposeAccounts(origin) + if (origin !== 'metamask' && !exposeAccounts) { return [] } + const isUnlocked = this.keyringController.memStore.getState().isUnlocked + const selectedAddress = this.preferencesController.getSelectedAddress() + // only show address if account is unlocked + if (isUnlocked && selectedAddress) { + return [selectedAddress] + } else { + return [] } - return [] // changing this is a breaking change }, // tx signing processTransaction: this.newUnapprovedTransaction.bind(this), @@ -371,7 +371,7 @@ 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. */ - createPublicConfigStore () { + createPublicConfigStore ({ checkIsEnabled }) { // subset of state for metamask inpage provider const publicConfigStore = new ObservableStore() @@ -384,16 +384,24 @@ module.exports = class MetamaskController extends EventEmitter { } function updatePublicConfigStore (memState) { - publicConfigStore.putState(selectPublicState(memState)) + const publicState = selectPublicState(memState) + publicConfigStore.putState(publicState) } - function selectPublicState ({ isUnlocked, network, provider }) { - return { + function selectPublicState ({ isUnlocked, selectedAddress, network, completedOnboarding, provider }) { + const isEnabled = checkIsEnabled() + const isReady = isUnlocked && isEnabled + const result = { isUnlocked, + isEnabled, + selectedAddress: isReady ? selectedAddress : null, networkVersion: network, + onboardingcomplete: completedOnboarding, chainId: selectChainId({ network, provider }), } + return result } + return publicConfigStore } @@ -425,13 +433,13 @@ module.exports = class MetamaskController extends EventEmitter { */ getApi () { const keyringController = this.keyringController - const networkController = this.networkController - const onboardingController = this.onboardingController - const permissionsController = this.permissionsController const preferencesController = this.preferencesController + const txController = this.txController + const networkController = this.networkController + const providerApprovalController = this.providerApprovalController + const onboardingController = this.onboardingController const threeBoxController = this.threeBoxController const abTestController = this.abTestController - const txController = this.txController return { // etc @@ -489,6 +497,7 @@ module.exports = class MetamaskController extends EventEmitter { setPreference: nodeify(preferencesController.setPreference, preferencesController), completeOnboarding: nodeify(preferencesController.completeOnboarding, preferencesController), addKnownMethodData: nodeify(preferencesController.addKnownMethodData, preferencesController), + unsetMigratedPrivacyMode: nodeify(preferencesController.unsetMigratedPrivacyMode, preferencesController), // BlacklistController whitelistPhishingDomain: this.whitelistPhishingDomain.bind(this), @@ -536,6 +545,11 @@ module.exports = class MetamaskController extends EventEmitter { signTypedMessage: nodeify(this.signTypedMessage, this), cancelTypedMessage: this.cancelTypedMessage.bind(this), + // provider approval + approveProviderRequestByOrigin: providerApprovalController.approveProviderRequestByOrigin.bind(providerApprovalController), + rejectProviderRequestByOrigin: providerApprovalController.rejectProviderRequestByOrigin.bind(providerApprovalController), + clearApprovedOrigins: providerApprovalController.clearApprovedOrigins.bind(providerApprovalController), + // onboarding controller setSeedPhraseBackedUp: nodeify(onboardingController.setSeedPhraseBackedUp, onboardingController), @@ -549,21 +563,10 @@ module.exports = class MetamaskController extends EventEmitter { // a/b test controller getAssignedABTestGroupName: nodeify(abTestController.getAssignedABTestGroupName, abTestController), - - // permissions - approvePermissionsRequest: nodeify(permissionsController.approvePermissionsRequest, permissionsController), - clearPermissions: permissionsController.clearPermissions.bind(permissionsController), - getApprovedAccounts: nodeify(permissionsController.getAccounts.bind(permissionsController)), - rejectPermissionsRequest: nodeify(permissionsController.rejectPermissionsRequest, permissionsController), - removePermissionsFor: permissionsController.removePermissionsFor.bind(permissionsController), - updateExposedAccounts: nodeify(permissionsController.updateExposedAccounts, permissionsController), - legacyExposeAccounts: nodeify(permissionsController.legacyExposeAccounts, permissionsController), - - getRequestAccountTabIds: (cb) => cb(null, this.getRequestAccountTabIds()), - getOpenMetamaskTabsIds: (cb) => cb(null, this.getOpenMetamaskTabsIds()), } } + //============================================================================= // VAULT / KEYRING RELATED METHODS //============================================================================= @@ -1343,10 +1346,10 @@ module.exports = class MetamaskController extends EventEmitter { // setup multiplexing const mux = setupMultiplex(connectionStream) - - // messages between inpage and background - this.setupProviderConnection(mux.createStream('provider'), senderUrl, extensionId) - this.setupPublicConfig(mux.createStream('publicConfig')) + // connect features + const publicApi = this.setupPublicApi(mux.createStream('publicApi')) + this.setupProviderConnection(mux.createStream('provider'), senderUrl, extensionId, publicApi) + this.setupPublicConfig(mux.createStream('publicConfig'), senderUrl) } /** @@ -1402,9 +1405,7 @@ module.exports = class MetamaskController extends EventEmitter { this.activeControllerConnections-- this.emit('controllerConnectionChanged', this.activeControllerConnections) // report any error - if (err) { - log.error(err) - } + if (err) log.error(err) } ) dnode.on('remote', (remote) => { @@ -1424,15 +1425,13 @@ module.exports = class MetamaskController extends EventEmitter { * resource is an extension. * @param {object} publicApi - The public API */ - setupProviderConnection (outStream, senderUrl, extensionId) { - const origin = senderUrl.hostname - const engine = this.setupProviderEngine(senderUrl, extensionId) + setupProviderConnection (outStream, senderUrl, extensionId, publicApi) { + const getSiteMetadata = publicApi && publicApi.getSiteMetadata + const engine = this.setupProviderEngine(senderUrl, extensionId, getSiteMetadata) // setup connection const providerStream = createEngineStream({ engine }) - const connectionId = this.addConnection(origin, { engine }) - pump( outStream, providerStream, @@ -1444,10 +1443,7 @@ module.exports = class MetamaskController extends EventEmitter { mid.destroy() } }) - connectionId && this.removeConnection(origin, connectionId) - if (err) { - log.error(err) - } + if (err) log.error(err) } ) } @@ -1455,8 +1451,7 @@ module.exports = class MetamaskController extends EventEmitter { /** * A method for creating a provider that is safely restricted for the requesting domain. **/ - setupProviderEngine (senderUrl, extensionId) { - + setupProviderEngine (senderUrl, extensionId, getSiteMetadata) { const origin = senderUrl.hostname // setup json rpc engine stack const engine = new RpcEngine() @@ -1470,17 +1465,20 @@ module.exports = class MetamaskController extends EventEmitter { const subscriptionManager = createSubscriptionManager({ provider, blockTracker }) subscriptionManager.events.on('notification', (message) => engine.emit('notification', message)) - // append origin to each request + // metadata engine.push(createOriginMiddleware({ origin })) - // logging engine.push(createLoggerMiddleware({ origin })) // filter and subscription polyfills engine.push(filterMiddleware) engine.push(subscriptionManager.middleware) - // permissions - engine.push(this.permissionsController.createMiddleware({ origin, extensionId })) // watch asset engine.push(this.preferencesController.requestWatchAsset.bind(this.preferencesController)) + // requestAccounts + engine.push(this.providerApprovalController.createMiddleware({ + senderUrl, + extensionId, + getSiteMetadata, + })) // forward to metamask primary provider engine.push(providerAsMiddleware(provider)) return engine @@ -1495,9 +1493,13 @@ module.exports = class MetamaskController extends EventEmitter { * this is a good candidate for deprecation. * * @param {*} outStream - The stream to provide public config over. + * @param {URL} senderUrl - The URL of requesting resource */ - setupPublicConfig (outStream) { - const configStore = this.createPublicConfigStore() + setupPublicConfig (outStream, senderUrl) { + const configStore = this.createPublicConfigStore({ + // check the providerApprovalController's approvedOrigins + checkIsEnabled: () => this.providerApprovalController.shouldExposeAccounts(senderUrl.hostname), + }) const configStream = asStream(configStore) pump( @@ -1506,145 +1508,43 @@ module.exports = class MetamaskController extends EventEmitter { (err) => { configStore.destroy() configStream.destroy() - if (err) { - log.error(err) - } + if (err) log.error(err) } ) } - // manage external connections - - onMessage (message, sender, sendResponse) { - if (!message || !message.type) { - log.debug(`Ignoring invalid message: '${JSON.stringify(message)}'`) - return - } - - let handleMessage - - try { - if (message.type === 'metamask:registerOnboarding') { - assert(sender.tab, 'Missing tab from sender') - assert(sender.tab.id && sender.tab.id !== extension.tabs.TAB_ID_NONE, 'Missing tab ID from sender') - assert(message.location, 'Missing location from message') - - handleMessage = this.onboardingController.registerOnboarding(message.location, sender.tab.id) - } else { - throw new Error(`Unrecognized message type: '${message.type}'`) + /** + * 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) } - } catch (error) { - console.error(error) - sendResponse(error) - return true + ) + + const getRemote = createDnodeRemoteGetter(dnode) + + const publicApi = { + // wrap with an await remote + getSiteMetadata: async () => { + const remote = await getRemote() + return await pify(remote.getSiteMetadata)() + }, } - if (handleMessage) { - handleMessage - .then(() => { - sendResponse(null, true) - }) - .catch((error) => { - console.error(error) - sendResponse(error) - }) - return true - } + return publicApi } - /** - * Adds a reference to a connection by origin. Ignores the 'metamask' origin. - * Caller must ensure that the returned id is stored such that the reference - * can be deleted later. - * - * @param {string} origin - The connection's origin string. - * @param {Object} options - Data associated with the connection - * @param {Object} options.engine - The connection's JSON Rpc Engine - * @returns {string} - The connection's id (so that it can be deleted later) - */ - addConnection (origin, { engine }) { - - if (origin === 'metamask') { - return null - } - - if (!this.connections[origin]) { - this.connections[origin] = {} - } - - const id = nanoid() - this.connections[origin][id] = { - engine, - } - - return id - } - - /** - * Deletes a reference to a connection, by origin and id. - * Ignores unknown origins. - * - * @param {string} origin - The connection's origin string. - * @param {string} id - The connection's id, as returned from addConnection. - */ - removeConnection (origin, id) { - - const connections = this.connections[origin] - if (!connections) { - return - } - - delete connections[id] - - if (Object.keys(connections.length === 0)) { - delete this.connections[origin] - } - } - - /** - * Causes the RPC engines associated with the connections to the given origin - * to emit a notification event with the given payload. - * Does nothing if the extension is locked or the origin is unknown. - * - * @param {string} origin - The connection's origin string. - * @param {any} payload - The event payload. - */ - notifyConnections (origin, payload) { - - const { isUnlocked } = this.getState() - const connections = this.connections[origin] - if (!isUnlocked || !connections) { - return - } - - Object.values(connections).forEach(conn => { - conn.engine && conn.engine.emit('notification', payload) - }) - } - - /** - * Causes the RPC engines associated with all connections to emit a - * notification event with the given payload. - * Does nothing if the extension is locked. - * - * @param {any} payload - The event payload. - */ - notifyAllConnections (payload) { - - const { isUnlocked } = this.getState() - if (!isUnlocked) { - return - } - - Object.values(this.connections).forEach(origin => { - Object.values(origin).forEach(conn => { - conn.engine && conn.engine.emit('notification', payload) - }) - }) - } - - // handlers - /** * Handle a KeyringController update * @param {object} state the KC state @@ -1673,8 +1573,6 @@ module.exports = class MetamaskController extends EventEmitter { } } - // misc - /** * A method for emitting the full MetaMask state to all registered listeners. * @private @@ -1683,10 +1581,6 @@ module.exports = class MetamaskController extends EventEmitter { this.emit('update', this.getState()) } - //============================================================================= - // MISCELLANEOUS - //============================================================================= - /** * A method for estimating a good gas price at recent prices. * Returns the lowest price that would have been included in @@ -1783,14 +1677,10 @@ module.exports = class MetamaskController extends EventEmitter { * @param {string} amount - The amount of ether desired, as a base 10 string. */ buyEth (address, amount) { - if (!amount) { - amount = '5' - } + if (!amount) amount = '5' const network = this.networkController.getNetworkState() const url = getBuyEthUrl({ network, address, amount }) - if (url) { - this.platform.openWindow({ url }) - } + if (url) this.platform.openWindow({ url }) } /** diff --git a/app/scripts/migrations/004.js b/app/scripts/migrations/004.js index a1ae823cd..cd558300c 100644 --- a/app/scripts/migrations/004.js +++ b/app/scripts/migrations/004.js @@ -9,9 +9,7 @@ module.exports = { const safeVersionedData = clone(versionedData) safeVersionedData.meta.version = version try { - if (safeVersionedData.data.config.provider.type !== 'rpc') { - return Promise.resolve(safeVersionedData) - } + if (safeVersionedData.data.config.provider.type !== 'rpc') return Promise.resolve(safeVersionedData) switch (safeVersionedData.data.config.provider.rpcTarget) { case 'https://testrpc.metamask.io/': safeVersionedData.data.config.provider = { @@ -23,7 +21,6 @@ module.exports = { type: 'mainnet', } break - // No default } } catch (_) {} return Promise.resolve(safeVersionedData) diff --git a/app/scripts/migrations/015.js b/app/scripts/migrations/015.js index 3d20b58db..5e2f9e63b 100644 --- a/app/scripts/migrations/015.js +++ b/app/scripts/migrations/015.js @@ -32,11 +32,8 @@ function transformState (state) { if (TransactionController && TransactionController.transactions) { const transactions = TransactionController.transactions newState.TransactionController.transactions = transactions.map((txMeta) => { - if (!txMeta.err) { - return txMeta - } else if (txMeta.err.message === 'Gave up submitting tx.') { - txMeta.status = 'failed' - } + if (!txMeta.err) return txMeta + else if (txMeta.err.message === 'Gave up submitting tx.') txMeta.status = 'failed' return txMeta }) } diff --git a/app/scripts/migrations/016.js b/app/scripts/migrations/016.js index 76a106ca7..048c7a40e 100644 --- a/app/scripts/migrations/016.js +++ b/app/scripts/migrations/016.js @@ -33,9 +33,7 @@ function transformState (state) { const transactions = newState.TransactionController.transactions newState.TransactionController.transactions = transactions.map((txMeta) => { - if (!txMeta.err) { - return txMeta - } + if (!txMeta.err) return txMeta if (txMeta.err === 'transaction with the same hash was already imported.') { txMeta.status = 'submitted' delete txMeta.err diff --git a/app/scripts/migrations/017.js b/app/scripts/migrations/017.js index 918df4ade..5f6d906d6 100644 --- a/app/scripts/migrations/017.js +++ b/app/scripts/migrations/017.js @@ -31,9 +31,7 @@ function transformState (state) { if (TransactionController && TransactionController.transactions) { const transactions = newState.TransactionController.transactions newState.TransactionController.transactions = transactions.map((txMeta) => { - if (!txMeta.status === 'failed') { - return txMeta - } + if (!txMeta.status === 'failed') return txMeta if (txMeta.retryCount > 0 && txMeta.retryCount < 2) { txMeta.status = 'submitted' delete txMeta.err diff --git a/app/scripts/migrations/019.js b/app/scripts/migrations/019.js index f560f5579..7b726c3e8 100644 --- a/app/scripts/migrations/019.js +++ b/app/scripts/migrations/019.js @@ -35,9 +35,7 @@ function transformState (state) { const transactions = newState.TransactionController.transactions newState.TransactionController.transactions = transactions.map((txMeta, _, txList) => { - if (txMeta.status !== 'submitted') { - return txMeta - } + if (txMeta.status !== 'submitted') return txMeta const confirmedTxs = txList.filter((tx) => tx.status === 'confirmed') .filter((tx) => tx.txParams.from === txMeta.txParams.from) diff --git a/app/scripts/migrations/022.js b/app/scripts/migrations/022.js index eeb142b7a..1fbe241e6 100644 --- a/app/scripts/migrations/022.js +++ b/app/scripts/migrations/022.js @@ -33,9 +33,7 @@ function transformState (state) { const transactions = newState.TransactionController.transactions newState.TransactionController.transactions = transactions.map((txMeta) => { - if (txMeta.status !== 'submitted' || txMeta.submittedTime) { - return txMeta - } + if (txMeta.status !== 'submitted' || txMeta.submittedTime) return txMeta txMeta.submittedTime = (new Date()).getTime() return txMeta }) diff --git a/app/scripts/migrations/023.js b/app/scripts/migrations/023.js index 84532537e..18493a789 100644 --- a/app/scripts/migrations/023.js +++ b/app/scripts/migrations/023.js @@ -33,9 +33,7 @@ function transformState (state) { if (TransactionController && TransactionController.transactions) { const transactions = newState.TransactionController.transactions - if (transactions.length <= 40) { - return newState - } + if (transactions.length <= 40) return newState const reverseTxList = transactions.reverse() let stripping = true @@ -46,11 +44,8 @@ function transformState (state) { txMeta.status === 'confirmed' || txMeta.status === 'dropped') }) - if (txIndex < 0) { - stripping = false - } else { - reverseTxList.splice(txIndex, 1) - } + if (txIndex < 0) stripping = false + else reverseTxList.splice(txIndex, 1) } newState.TransactionController.transactions = reverseTxList.reverse() diff --git a/app/scripts/migrations/024.js b/app/scripts/migrations/024.js index fda463e1a..5ffaea377 100644 --- a/app/scripts/migrations/024.js +++ b/app/scripts/migrations/024.js @@ -25,9 +25,7 @@ module.exports = { function transformState (state) { const newState = state - if (!newState.TransactionController) { - return newState - } + if (!newState.TransactionController) return newState const transactions = newState.TransactionController.transactions newState.TransactionController.transactions = transactions.map((txMeta, _) => { if ( diff --git a/app/scripts/migrations/025.js b/app/scripts/migrations/025.js index 48bff962d..fd4faa782 100644 --- a/app/scripts/migrations/025.js +++ b/app/scripts/migrations/025.js @@ -29,9 +29,7 @@ function transformState (state) { if (newState.TransactionController.transactions) { const transactions = newState.TransactionController.transactions newState.TransactionController.transactions = transactions.map((txMeta) => { - if (txMeta.status !== 'unapproved') { - return txMeta - } + if (txMeta.status !== 'unapproved') return txMeta txMeta.txParams = normalizeTxParams(txMeta.txParams) return txMeta }) @@ -56,9 +54,7 @@ function normalizeTxParams (txParams) { // apply only keys in the whiteList const normalizedTxParams = {} Object.keys(whiteList).forEach((key) => { - if (txParams[key]) { - normalizedTxParams[key] = whiteList[key](txParams[key]) - } + if (txParams[key]) normalizedTxParams[key] = whiteList[key](txParams[key]) }) return normalizedTxParams diff --git a/app/scripts/migrations/029.js b/app/scripts/migrations/029.js index 59724c154..e17479ccc 100644 --- a/app/scripts/migrations/029.js +++ b/app/scripts/migrations/029.js @@ -24,3 +24,4 @@ module.exports = { return isApproved && now - createdTime > unacceptableDelay }), } + diff --git a/app/scripts/migrations/031.js b/app/scripts/migrations/031.js index 927de98c4..9c8cbeb09 100644 --- a/app/scripts/migrations/031.js +++ b/app/scripts/migrations/031.js @@ -3,7 +3,7 @@ const version = 31 const clone = require('clone') /* - * The purpose of this migration is to properly set the completedOnboarding flag based on the state + * The purpose of this migration is to properly set the completedOnboarding flag baesd on the state * of the KeyringController. */ module.exports = { diff --git a/app/scripts/migrations/040.js b/app/scripts/migrations/040.js deleted file mode 100644 index 042f499b2..000000000 --- a/app/scripts/migrations/040.js +++ /dev/null @@ -1,23 +0,0 @@ -const version = 40 -const clone = require('clone') - -/** - * Site connections are now managed by the PermissionsController, and the - * ProviderApprovalController is removed. This migration deletes all - * ProviderApprovalController state. - */ -module.exports = { - version, - migrate: async function (originalVersionedData) { - const versionedData = clone(originalVersionedData) - versionedData.meta.version = version - const state = versionedData.data - versionedData.data = transformState(state) - return versionedData - }, -} - -function transformState (state) { - delete state.ProviderApprovalController - return state -} diff --git a/app/scripts/platforms/extension.js b/app/scripts/platforms/extension.js index 5ae05d230..d54a8a7b3 100644 --- a/app/scripts/platforms/extension.js +++ b/app/scripts/platforms/extension.js @@ -1,7 +1,7 @@ const extension = require('extensionizer') const {createExplorerLink: explorerLink} = require('etherscan-link') -const { getEnvironmentType, checkForError } = require('../lib/util') +const {getEnvironmentType} = require('../lib/util') const {ENVIRONMENT_TYPE_BACKGROUND} = require('../lib/enums') class ExtensionPlatform { @@ -66,58 +66,6 @@ class ExtensionPlatform { } } - queryTabs () { - return new Promise((resolve, reject) => { - extension.tabs.query({}, tabs => { - const err = checkForError() - if (err) { - reject(err) - } else { - resolve(tabs) - } - }) - }) - } - - currentTab () { - return new Promise((resolve, reject) => { - extension.tabs.getCurrent(tab => { - const err = checkForError() - if (err) { - reject(err) - } else { - resolve(tab) - } - }) - }) - } - - switchToTab (tabId) { - return new Promise((resolve, reject) => { - extension.tabs.update(tabId, {highlighted: true}, (tab) => { - const err = checkForError() - if (err) { - reject(err) - } else { - resolve(tab) - } - }) - }) - } - - closeTab (tabId) { - return new Promise((resolve, reject) => { - extension.tabs.remove(tabId, () => { - const err = checkForError() - if (err) { - reject(err) - } else { - resolve() - } - }) - }) - } - _showConfirmedTransaction (txMeta) { this._subscribeToNotificationClicked() diff --git a/development/announcer.js b/development/announcer.js index 8b0c1de27..ea1bfdd36 100644 --- a/development/announcer.js +++ b/development/announcer.js @@ -1,11 +1,11 @@ -const manifest = require('../app/manifest.json') -const version = manifest.version +var manifest = require('../app/manifest.json') +var version = manifest.version -const fs = require('fs') -const path = require('path') -const changelog = fs.readFileSync(path.join(__dirname, '..', 'CHANGELOG.md')).toString() +var fs = require('fs') +var path = require('path') +var changelog = fs.readFileSync(path.join(__dirname, '..', 'CHANGELOG.md')).toString() -const log = changelog.split(version)[1].split('##')[0].trim() +var log = changelog.split(version)[1].split('##')[0].trim() const msg = `*MetaMask ${version}* now published! It should auto-update soon!\n${log}` diff --git a/development/auto-changelog.sh b/development/auto-changelog.sh index 26ab8e93f..3ed059b3d 100755 --- a/development/auto-changelog.sh +++ b/development/auto-changelog.sh @@ -10,7 +10,7 @@ git fetch --tags most_recent_tag="$(git describe --tags "$(git rev-list --tags --max-count=1)")" -git rev-list "${most_recent_tag}"..HEAD | while read -r commit +git rev-list "${most_recent_tag}"..HEAD | while read commit do subject="$(git show -s --format="%s" "$commit")" diff --git a/development/mock-3box.js b/development/mock-3box.js index a26e02a82..53f228f21 100644 --- a/development/mock-3box.js +++ b/development/mock-3box.js @@ -28,9 +28,7 @@ class Mock3Box { static openBox (address) { this.address = address return Promise.resolve({ - onSyncDone: cb => { - setTimeout(cb, 200) - }, + onSyncDone: cb => { setTimeout(cb, 200) }, openSpace: async (spaceName, config) => { const { onSyncDone } = config this.spaceName = spaceName diff --git a/development/mock-dev.js b/development/mock-dev.js index 51a8cde46..8da625149 100644 --- a/development/mock-dev.js +++ b/development/mock-dev.js @@ -10,13 +10,13 @@ * without having to re-open the plugin or even re-build it. */ -import React from 'react' const render = require('react-dom').render +const h = require('react-hyperscript') const Root = require('../ui/app/pages') const configureStore = require('../ui/app/store/store') const actions = require('../ui/app/store/actions') const backGroundConnectionModifiers = require('./backGroundConnectionModifiers') -import Selector from './selector' +const Selector = require('./selector') const MetamaskController = require('../app/scripts/metamask-controller') const firstTimeState = require('../app/scripts/first-time-state') const ExtensionPlatform = require('../app/scripts/platforms/extension') @@ -94,7 +94,7 @@ function modifyBackgroundConnection (backgroundConnectionModifier) { } // parse opts -const store = configureStore(firstState) +var store = configureStore(firstState) // start app startApp() @@ -106,38 +106,40 @@ function startApp () { body.appendChild(container) render( -
- - -
- -
-
, - container, - ) + }, + }, [ + h(Root, { + store: store, + }), + ]), + + ] + ), container) } diff --git a/development/rollback.sh b/development/rollback.sh index a3040e6f1..639d72a67 100755 --- a/development/rollback.sh +++ b/development/rollback.sh @@ -4,20 +4,20 @@ echo "Rolling back to version $1" # Checkout branch to increment version -git checkout -b "version-increment-$1" +git checkout -b version-increment-$1 yarn version:bump patch # Store the new version name -NEW_VERSION=$(jq -r .version < app/manifest.json) +NEW_VERSION=$(cat app/manifest.json | jq -r .version) # Make sure origin tags are loaded git fetch origin # check out the rollback branch -git checkout "origin/v$1" +git checkout origin/v$1 # Create the rollback branch. -git checkout -b "Version-$NEW_VERSION-Rollback-to-$1" +git checkout -b Version-$NEW_VERSION-Rollback-to-$1 # Set the version files to the next one. git checkout master CHANGELOG.md @@ -28,8 +28,8 @@ git commit -m "Version $NEW_VERSION (Rollback to $1)" git push -u origin HEAD # Create tag and push that up too -git tag "v${NEW_VERSION}" -git push origin "v${NEW_VERSION}" +git tag v${NEW_VERSION} +git push origin v${NEW_VERSION} # Cleanup version branch -git branch -D "version-increment-$1" +git branch -D version-increment-$1 diff --git a/development/selector.js b/development/selector.js index 01011c710..2673d0fe3 100644 --- a/development/selector.js +++ b/development/selector.js @@ -1,49 +1,42 @@ -import PropTypes from 'prop-types' -import React, {Component} from 'react' +const Component = require('react').Component +const h = require('react-hyperscript') +const inherits = require('util').inherits -export default class Selector extends Component { - state = {} +module.exports = NewComponent - render () { - const { - states, - selectedKey, - actions, - store, - modifyBackgroundConnection, - backGroundConnectionModifiers, - } = this.props - const selected = this.state.selected || selectedKey - - return ( - - ) - } +inherits(NewComponent, Component) +function NewComponent () { + Component.call(this) } -Selector.propTypes = { - states: PropTypes.object.isRequired, - selectedKey: PropTypes.string.isRequired, - actions: PropTypes.object.isRequired, - store: PropTypes.object.isRequired, - modifyBackgroundConnection: PropTypes.func.isRequired, - backGroundConnectionModifiers: PropTypes.object.isRequired, +NewComponent.prototype.render = function () { + const props = this.props + const { + states, + selectedKey, + actions, + store, + modifyBackgroundConnection, + backGroundConnectionModifiers, + } = props + + const state = this.state || {} + const selected = state.selected || selectedKey + + return h('select', { + style: { + margin: '20px 20px 0px', + }, + value: selected, + onChange: (event) => { + const selectedKey = event.target.value + const backgroundConnectionModifier = backGroundConnectionModifiers[selectedKey] + modifyBackgroundConnection(backgroundConnectionModifier || {}) + store.dispatch(actions.update(selectedKey)) + this.setState({ selected: selectedKey }) + }, + }, Object.keys(states).map((stateName) => { + return h('option', { value: stateName }, stateName) + })) + } diff --git a/development/show-deps-install-scripts.js b/development/show-deps-install-scripts.js index 419c9d25f..03a9bb859 100644 --- a/development/show-deps-install-scripts.js +++ b/development/show-deps-install-scripts.js @@ -5,9 +5,7 @@ const readInstalled = require('read-installed') const installScripts = ['preinstall', 'install', 'postinstall'] readInstalled('./', { dev: true }, function (err, data) { - if (err) { - throw err - } + if (err) throw err const deps = data.dependencies Object.entries(deps).forEach(([packageName, packageData]) => { @@ -15,20 +13,12 @@ readInstalled('./', { dev: true }, function (err, data) { const scriptKeys = Reflect.ownKeys(packageScripts) const hasInstallScript = installScripts.some(installKey => scriptKeys.includes(installKey)) - if (!hasInstallScript) { - return - } + if (!hasInstallScript) return const matchingScripts = {} - if (packageScripts.preinstall) { - matchingScripts.preinstall = packageScripts.preinstall - } - if (packageScripts.install) { - matchingScripts.install = packageScripts.install - } - if (packageScripts.postinstall) { - matchingScripts.postinstall = packageScripts.postinstall - } + if (packageScripts.preinstall) matchingScripts.preinstall = packageScripts.preinstall + if (packageScripts.install) matchingScripts.install = packageScripts.install + if (packageScripts.postinstall) matchingScripts.postinstall = packageScripts.postinstall const scriptNames = Reflect.ownKeys(matchingScripts) const relativePath = path.relative(process.cwd(), packageData.path) diff --git a/development/sourcemap-validator.js b/development/sourcemap-validator.js index 22c448862..546745f16 100644 --- a/development/sourcemap-validator.js +++ b/development/sourcemap-validator.js @@ -52,9 +52,7 @@ async function validateSourcemapForFile ({ buildName }) { const consumer = await new SourceMapConsumer(rawSourceMap) const hasContentsOfAllSources = consumer.hasContentsOfAllSources() - if (!hasContentsOfAllSources) { - console.warn('SourcemapValidator - missing content of some sources...') - } + if (!hasContentsOfAllSources) console.warn('SourcemapValidator - missing content of some sources...') console.log(` sampling from ${consumer.sources.length} files`) let sampleCount = 0 @@ -96,10 +94,8 @@ async function validateSourcemapForFile ({ buildName }) { } function indicesOf (substring, string) { - const a = [] - let i = -1 - while ((i = string.indexOf(substring, i + 1)) >= 0) { - a.push(i) - } + var a = [] + var i = -1 + while ((i = string.indexOf(substring, i + 1)) >= 0) a.push(i) return a } diff --git a/development/static-server.js b/development/static-server.js deleted file mode 100644 index d8f22cabe..000000000 --- a/development/static-server.js +++ /dev/null @@ -1,92 +0,0 @@ -const fs = require('fs') -const http = require('http') -const path = require('path') - -const chalk = require('chalk') -const pify = require('pify') -const serveHandler = require('serve-handler') - -const fsStat = pify(fs.stat) -const DEFAULT_PORT = 9080 - -const onResponse = (request, response) => { - if (response.statusCode >= 400) { - console.log(chalk`{gray '-->'} {red ${response.statusCode}} ${request.url}`) - } else if (response.statusCode >= 200 && response.statusCode < 300) { - console.log(chalk`{gray '-->'} {green ${response.statusCode}} ${request.url}`) - } else { - console.log(chalk`{gray '-->'} {green.dim ${response.statusCode}} ${request.url}`) - } -} -const onRequest = (request, response) => { - console.log(chalk`{gray '<--'} {blue [${request.method}]} ${request.url}`) - response.on('finish', () => onResponse(request, response)) -} - -const startServer = ({ port, rootDirectory }) => { - const server = http.createServer((request, response) => { - if (request.url.startsWith('/node_modules/')) { - request.url = request.url.substr(14) - return serveHandler(request, response, { - directoryListing: false, - public: path.resolve('./node_modules'), - }) - } - return serveHandler(request, response, { - directoryListing: false, - public: rootDirectory, - }) - }) - - server.on('request', onRequest) - - server.listen(port, () => { - console.log(`Running at http://localhost:${port}`) - }) -} - -const parsePort = (portString) => { - const port = Number(portString) - if (!Number.isInteger(port)) { - throw new Error(`Port '${portString}' is invalid; must be an integer`) - } else if (port < 0 || port > 65535) { - throw new Error(`Port '${portString}' is out of range; must be between 0 and 65535 inclusive`) - } - return port -} - -const parseDirectoryArgument = async (pathString) => { - const resolvedPath = path.resolve(pathString) - const directoryStats = await fsStat(resolvedPath) - if (!directoryStats.isDirectory()) { - throw new Error(`Invalid path '${pathString}'; must be a directory`) - } - return resolvedPath -} - -const main = async () => { - const args = process.argv.slice(2) - - const options = { - port: process.env.port || DEFAULT_PORT, - rootDirectory: path.resolve('.'), - } - - while (args.length) { - if (/^(--port|-p)$/i.test(args[0])) { - if (args[1] === undefined) { - throw new Error('Missing port argument') - } - options.port = parsePort(args[1]) - args.splice(0, 2) - } else { - options.rootDirectory = await parseDirectoryArgument(args[0]) - args.splice(0, 1) - } - } - - startServer(options) -} - -main() - .catch(console.error) diff --git a/development/verify-locale-strings.js b/development/verify-locale-strings.js index c6875b36c..aa0c4503f 100644 --- a/development/verify-locale-strings.js +++ b/development/verify-locale-strings.js @@ -161,11 +161,11 @@ async function verifyEnglishLocale (fix = false) { const englishLocale = await getLocale('en') const javascriptFiles = await findJavascriptFiles(path.resolve(__dirname, '..', 'ui')) - const regex = /'(\w+)'|"(\w+)"/g + const regex = /'(\w+)'/g const usedMessages = new Set() for await (const fileContents of getFileContents(javascriptFiles)) { for (const match of matchAll.call(fileContents, regex)) { - usedMessages.add(match[1] || match[2]) + usedMessages.add(match[1]) } } diff --git a/development/version-bump.js b/development/version-bump.js index 6ae21b782..66b6baffe 100644 --- a/development/version-bump.js +++ b/development/version-bump.js @@ -40,8 +40,6 @@ function newVersionFrom (manifest, bumpType) { case 'patch': segments[2] += 1 break - default: - throw new Error(`invalid bumpType ${bumpType}`) } return segments.map(String).join('.') diff --git a/docs/porting_to_new_environment.md b/docs/porting_to_new_environment.md index 92f06f37d..9cb8e9c2d 100644 --- a/docs/porting_to_new_environment.md +++ b/docs/porting_to_new_environment.md @@ -18,8 +18,8 @@ const providerFromEngine = require('eth-json-rpc-middleware/providerFromEngine') /** * returns a provider restricted to the requesting domain **/ -function incomingConnection (domain) { - const engine = metamaskController.setupProviderEngine(domain) +function incomingConnection (domain, getSiteMetadata) { + const engine = metamaskController.setupProviderEngine(domain, getSiteMetadata) const provider = providerFromEngine(engine) return provider } @@ -32,6 +32,15 @@ const filterMiddleware = engine._middleware.filter(mid => mid.name === 'filterMi filterMiddleware.destroy() ``` +### getSiteMetadata() + +This method is used to enhance our confirmation screens with images and text representing the requesting domain. + +It should return a promise that resolves with an object with the following properties: + +- `name`: The requesting site's name. +- `icon`: A URI representing the site's logo. + ### Using the Streams Interface Only use this if you intend to construct the [metamask-inpage-provider](https://github.com/MetaMask/metamask-inpage-provider) over a stream! diff --git a/gulpfile.js b/gulpfile.js index d5d74333a..3e4a3b7ff 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -428,9 +428,7 @@ function createTasksForBuildJs ({ rootDir, taskPrefix, bundleTaskOpts, destinati // compose into larger task const subtasks = [] subtasks.push(gulp.parallel(buildPhase1.map(file => `${taskPrefix}:${file}`))) - if (buildPhase2.length) { - subtasks.push(gulp.parallel(buildPhase2.map(file => `${taskPrefix}:${file}`))) - } + if (buildPhase2.length) subtasks.push(gulp.parallel(buildPhase2.map(file => `${taskPrefix}:${file}`))) gulp.task(taskPrefix, gulp.series(subtasks)) } diff --git a/package.json b/package.json index 48683a14e..047911146 100644 --- a/package.json +++ b/package.json @@ -10,12 +10,10 @@ "start:test": "gulp dev:test", "build:test": "gulp build:test", "test": "yarn test:unit && yarn lint", - "dapp": "node development/static-server.js test/e2e/contract-test --port 8080", - "dapp-chain": "GANACHE_ARGS='-b 2' concurrently -k -n ganache,dapp -p '[{time}][{name}]' 'yarn ganache:start' 'sleep 5 && node development/static-server.js test/e2e/contract-test --port 8080'", - "forwarder": "node ./development/static-server.js ./node_modules/@metamask/forwarder/dist/ --port 9010", - "dapp-forwarder": "concurrently -k -n forwarder,dapp -p '[{time}][{name}]' 'yarn forwarder' 'yarn dapp'", + "dapp": "static-server test/e2e/contract-test --port 8080", + "dapp-chain": "GANACHE_ARGS='-b 2' concurrently -k -n ganache,dapp -p '[{time}][{name}]' 'yarn ganache:start' 'sleep 5 && static-server test/e2e/contract-test --port 8080'", "watch:test:unit": "nodemon --exec \"yarn test:unit\" ./test ./app ./ui", - "sendwithprivatedapp": "node development/static-server.js test/e2e/send-eth-with-private-key-test --port 8080", + "sendwithprivatedapp": "static-server test/e2e/send-eth-with-private-key-test --port 8080", "test:unit": "cross-env METAMASK_ENV=test mocha --exit --require test/setup.js --recursive \"test/unit/**/*.js\" \"ui/app/**/*.test.js\"", "test:unit:global": "mocha test/unit-global/*", "test:single": "cross-env METAMASK_ENV=test mocha --require test/helper.js", @@ -39,9 +37,7 @@ "lint:fix": "eslint . --ext js,json --fix", "lint:changed": "{ git ls-files --others --exclude-standard ; git diff-index --name-only --diff-filter=d HEAD ; } | grep --regexp='[.]js$' --regexp='[.]json$' | tr '\\n' '\\0' | xargs -0 eslint", "lint:changed:fix": "{ git ls-files --others --exclude-standard ; git diff-index --name-only --diff-filter=d HEAD ; } | grep --regexp='[.]js$' --regexp='[.]json$' | tr '\\n' '\\0' | xargs -0 eslint --fix", - "lint:shellcheck": "shellcheck --version && find . -type f -name '*.sh' ! -path './node_modules/*' -print0 | xargs -0 shellcheck", "verify-locales": "node ./development/verify-locale-strings.js", - "verify-locales:fix": "node ./development/verify-locale-strings.js --fix", "mozilla-lint": "addons-linter dist/firefox", "watch": "cross-env METAMASK_ENV=test mocha --watch --require test/setup.js --reporter min --recursive \"test/unit/**/*.js\" \"ui/app/**/*.test.js\"", "devtools:react": "react-devtools", @@ -93,7 +89,7 @@ "eth-block-tracker": "^4.4.2", "eth-contract-metadata": "^1.11.0", "eth-ens-namehash": "^2.0.8", - "eth-json-rpc-errors": "^2.0.0", + "eth-json-rpc-errors": "^1.1.0", "eth-json-rpc-filters": "^4.1.1", "eth-json-rpc-infura": "^4.0.1", "eth-json-rpc-middleware": "^4.2.0", @@ -116,7 +112,6 @@ "ethjs-query": "^0.3.4", "extension-port-stream": "^1.0.0", "extensionizer": "^1.0.1", - "fast-deep-equal": "^2.0.1", "fast-json-patch": "^2.0.4", "fuse.js": "^3.2.0", "gaba": "^1.9.0", @@ -129,11 +124,10 @@ "lodash.shuffle": "^4.2.0", "loglevel": "^1.4.1", "luxon": "^1.8.2", - "metamask-inpage-provider": "^4.0.2", + "metamask-inpage-provider": "^3.0.0", "metamask-logo": "^2.1.4", "mkdirp": "^0.5.1", "multihashes": "^0.4.12", - "nanoid": "^2.1.6", "nonce-tracker": "^1.0.0", "number-to-bn": "^1.7.0", "obj-multiplex": "^1.0.0", @@ -175,9 +169,7 @@ "redux-thunk": "^2.2.0", "request-promise": "^4.2.1", "reselect": "^3.0.1", - "rpc-cap": "^1.0.1", "safe-event-emitter": "^1.0.1", - "safe-json-stringify": "^1.2.0", "single-call-balance-checker-abi": "^1.0.0", "string.prototype.matchall": "^3.0.1", "swappable-obj-proxy": "^1.1.0", @@ -196,8 +188,6 @@ "@babel/preset-env": "^7.5.5", "@babel/preset-react": "^7.0.0", "@babel/register": "^7.5.5", - "@metamask/forwarder": "^1.0.0", - "@metamask/onboarding": "^0.1.2", "@sentry/cli": "^1.30.3", "@storybook/addon-actions": "^5.2.6", "@storybook/addon-info": "^5.1.1", @@ -210,7 +200,6 @@ "browserify": "^16.2.3", "browserify-transform-tools": "^1.7.0", "chai": "^4.1.0", - "chalk": "^2.4.2", "chromedriver": "^2.41.0", "concurrently": "^4.1.1", "coveralls": "^3.0.0", @@ -289,12 +278,12 @@ "rimraf": "^2.6.2", "sass-loader": "^7.0.1", "selenium-webdriver": "^3.5.0", - "serve-handler": "^6.1.2", "sesify": "^4.2.1", "sesify-viz": "^3.0.5", "sinon": "^5.0.0", "source-map": "^0.7.2", "source-map-explorer": "^2.0.1", + "static-server": "^2.2.1", "style-loader": "^0.21.0", "stylelint": "^9.10.1", "stylelint-config-standard": "^18.2.0", diff --git a/test/e2e/contract-test/contract.js b/test/e2e/contract-test/contract.js index 6c3941b9d..ebfea34ec 100644 --- a/test/e2e/contract-test/contract.js +++ b/test/e2e/contract-test/contract.js @@ -1,4 +1,4 @@ -/*global ethereum, MetamaskOnboarding */ +/*global ethereum*/ /* The `piggybankContract` is compiled from: @@ -30,14 +30,8 @@ The `piggybankContract` is compiled from: } */ -const forwarderOrigin = 'http://localhost:9010' - -const isMetaMaskInstalled = () => { - return Boolean(window.ethereum && window.ethereum.isMetaMask) -} - -const initialize = () => { - const onboardButton = document.getElementById('connectButton') +web3.currentProvider.enable().then(() => { + var piggybankContract = web3.eth.contract([{'constant': false, 'inputs': [{'name': 'withdrawAmount', 'type': 'uint256'}], 'name': 'withdraw', 'outputs': [{'name': 'remainingBal', 'type': 'uint256'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'owner', 'outputs': [{'name': '', 'type': 'address'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [], 'name': 'deposit', 'outputs': [{'name': '', 'type': 'uint256'}], 'payable': true, 'stateMutability': 'payable', 'type': 'function'}, {'inputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'constructor'}]) const deployButton = document.getElementById('deployButton') const depositButton = document.getElementById('depositButton') const withdrawButton = document.getElementById('withdrawButton') @@ -48,307 +42,200 @@ const initialize = () => { const transferTokensWithoutGas = document.getElementById('transferTokensWithoutGas') const approveTokensWithoutGas = document.getElementById('approveTokensWithoutGas') const signTypedData = document.getElementById('signTypedData') - const signTypedDataResults = document.getElementById('signTypedDataResult') - const getAccountsButton = document.getElementById('getAccounts') - const getAccountsResults = document.getElementById('getAccountsResult') - const contractStatus = document.getElementById('contractStatus') - const tokenAddress = document.getElementById('tokenAddress') - const networkDiv = document.getElementById('network') - const chainIdDiv = document.getElementById('chainId') - const accountsDiv = document.getElementById('accounts') + deployButton.addEventListener('click', async function () { + document.getElementById('contractStatus').innerHTML = 'Deploying' - let onboarding - try { - onboarding = new MetamaskOnboarding({ forwarderOrigin }) - } catch (error) { - console.error(error) - } - let accounts - let piggybankContract - - const accountButtons = [ - deployButton, - depositButton, - withdrawButton, - sendButton, - createToken, - transferTokens, - approveTokens, - transferTokensWithoutGas, - approveTokensWithoutGas, - ] - - for (const button of accountButtons) { - button.disabled = true - } - - const isMetaMaskConnected = () => accounts && accounts.length > 0 - - const onClickInstall = () => { - onboardButton.innerText = 'Onboarding in progress' - onboardButton.disabled = true - onboarding.startOnboarding() - } - - const onClickConnect = async () => { - await window.ethereum.enable() - } - - const updateButtons = () => { - const accountButtonsDisabled = !isMetaMaskInstalled() || !isMetaMaskConnected() - if (accountButtonsDisabled) { - for (const button of accountButtons) { - button.disabled = true - } - } else { - deployButton.disabled = false - sendButton.disabled = false - createToken.disabled = false - } - - if (!isMetaMaskInstalled()) { - onboardButton.innerText = 'Click here to install MetaMask!' - onboardButton.onclick = onClickInstall - } else if (isMetaMaskConnected()) { - onboardButton.innerText = 'Connected' - onboardButton.disabled = true - if (onboarding) { - onboarding.stopOnboarding() - } - } else { - onboardButton.innerText = 'Connect' - onboardButton.onclick = onClickConnect - } - } - - const initializeAccountButtons = () => { - piggybankContract = web3.eth.contract([{'constant': false, 'inputs': [{'name': 'withdrawAmount', 'type': 'uint256'}], 'name': 'withdraw', 'outputs': [{'name': 'remainingBal', 'type': 'uint256'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'owner', 'outputs': [{'name': '', 'type': 'address'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [], 'name': 'deposit', 'outputs': [{'name': '', 'type': 'uint256'}], 'payable': true, 'stateMutability': 'payable', 'type': 'function'}, {'inputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'constructor'}]) - deployButton.onclick = async () => { - contractStatus.innerHTML = 'Deploying' - - const piggybank = await piggybankContract.new( - { - from: accounts[0], - data: '0x608060405234801561001057600080fd5b5033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000808190555061023b806100686000396000f300608060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632e1a7d4d1461005c5780638da5cb5b1461009d578063d0e30db0146100f4575b600080fd5b34801561006857600080fd5b5061008760048036038101908080359060200190929190505050610112565b6040518082815260200191505060405180910390f35b3480156100a957600080fd5b506100b26101d0565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6100fc6101f6565b6040518082815260200191505060405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561017057600080fd5b8160008082825403925050819055503373ffffffffffffffffffffffffffffffffffffffff166108fc839081150290604051600060405180830381858888f193505050501580156101c5573d6000803e3d6000fd5b506000549050919050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60003460008082825401925050819055506000549050905600a165627a7a72305820f237db3ec816a52589d82512117bc85bc08d3537683ffeff9059108caf3e5d400029', - gas: '4700000', - }, (error, contract) => { - if (error) { - contractStatus.innerHTML = 'Deployment Failed' - throw error - } else if (contract.address === undefined) { - return - } - - console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash) - contractStatus.innerHTML = 'Deployed' - depositButton.disabled = false - withdrawButton.disabled = false - - depositButton.onclick = () => { - contractStatus.innerHTML = 'Deposit initiated' - contract.deposit( - { - from: accounts[0], - value: '0x3782dace9d900000', - }, - (result) => { - console.log(result) - contractStatus.innerHTML = 'Deposit completed' - } - ) - } - withdrawButton.onclick = () => { - contract.withdraw( - '0xde0b6b3a7640000', - { from: accounts[0] }, - (result) => { - console.log(result) - contractStatus.innerHTML = 'Withdrawn' - } - ) - } + var piggybank = await piggybankContract.new( + { + from: web3.eth.accounts[0], + data: '0x608060405234801561001057600080fd5b5033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000808190555061023b806100686000396000f300608060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632e1a7d4d1461005c5780638da5cb5b1461009d578063d0e30db0146100f4575b600080fd5b34801561006857600080fd5b5061008760048036038101908080359060200190929190505050610112565b6040518082815260200191505060405180910390f35b3480156100a957600080fd5b506100b26101d0565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6100fc6101f6565b6040518082815260200191505060405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561017057600080fd5b8160008082825403925050819055503373ffffffffffffffffffffffffffffffffffffffff166108fc839081150290604051600060405180830381858888f193505050501580156101c5573d6000803e3d6000fd5b506000549050919050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60003460008082825401925050819055506000549050905600a165627a7a72305820f237db3ec816a52589d82512117bc85bc08d3537683ffeff9059108caf3e5d400029', + gas: '4700000', + }, function (e, contract) { + if (e) { + throw e } - ) - console.log(piggybank) - } - - sendButton.onclick = () => { - web3.eth.sendTransaction({ - from: accounts[0], - to: '0x2f318C334780961FB129D2a6c30D0763d9a5C970', - value: '0x29a2241af62c0000', - gas: 21000, - gasPrice: 20000000000, - }, (result) => { - console.log(result) - }) - } - - createToken.onclick = async () => { - const _initialAmount = 100 - const _tokenName = 'TST' - const _decimalUnits = 0 - const _tokenSymbol = 'TST' - const humanstandardtokenContract = web3.eth.contract([{'constant': true, 'inputs': [], 'name': 'name', 'outputs': [{'name': '', 'type': 'string'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [{'name': '_spender', 'type': 'address'}, {'name': '_value', 'type': 'uint256'}], 'name': 'approve', 'outputs': [{'name': 'success', 'type': 'bool'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'totalSupply', 'outputs': [{'name': '', 'type': 'uint256'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [{'name': '_from', 'type': 'address'}, {'name': '_to', 'type': 'address'}, {'name': '_value', 'type': 'uint256'}], 'name': 'transferFrom', 'outputs': [{'name': 'success', 'type': 'bool'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'decimals', 'outputs': [{'name': '', 'type': 'uint8'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'version', 'outputs': [{'name': '', 'type': 'string'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': true, 'inputs': [{'name': '_owner', 'type': 'address'}], 'name': 'balanceOf', 'outputs': [{'name': 'balance', 'type': 'uint256'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'symbol', 'outputs': [{'name': '', 'type': 'string'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [{'name': '_to', 'type': 'address'}, {'name': '_value', 'type': 'uint256'}], 'name': 'transfer', 'outputs': [{'name': 'success', 'type': 'bool'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': false, 'inputs': [{'name': '_spender', 'type': 'address'}, {'name': '_value', 'type': 'uint256'}, {'name': '_extraData', 'type': 'bytes'}], 'name': 'approveAndCall', 'outputs': [{'name': 'success', 'type': 'bool'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [{'name': '_owner', 'type': 'address'}, {'name': '_spender', 'type': 'address'}], 'name': 'allowance', 'outputs': [{'name': 'remaining', 'type': 'uint256'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'inputs': [{'name': '_initialAmount', 'type': 'uint256'}, {'name': '_tokenName', 'type': 'string'}, {'name': '_decimalUnits', 'type': 'uint8'}, {'name': '_tokenSymbol', 'type': 'string'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'constructor'}, {'payable': false, 'stateMutability': 'nonpayable', 'type': 'fallback'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': '_from', 'type': 'address'}, {'indexed': true, 'name': '_to', 'type': 'address'}, {'indexed': false, 'name': '_value', 'type': 'uint256'}], 'name': 'Transfer', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': '_owner', 'type': 'address'}, {'indexed': true, 'name': '_spender', 'type': 'address'}, {'indexed': false, 'name': '_value', 'type': 'uint256'}], 'name': 'Approval', 'type': 'event'}]) - - return humanstandardtokenContract.new( - _initialAmount, - _tokenName, - _decimalUnits, - _tokenSymbol, - { - from: accounts[0], - data: '0x60806040523480156200001157600080fd5b506040516200156638038062001566833981018060405260808110156200003757600080fd5b8101908080516401000000008111156200005057600080fd5b828101905060208101848111156200006757600080fd5b81518560018202830111640100000000821117156200008557600080fd5b50509291906020018051640100000000811115620000a257600080fd5b82810190506020810184811115620000b957600080fd5b8151856001820283011164010000000082111715620000d757600080fd5b5050929190602001805190602001909291908051906020019092919050505083838382600390805190602001906200011192919062000305565b5081600490805190602001906200012a92919062000305565b5080600560006101000a81548160ff021916908360ff1602179055505050506200016433826200016e640100000000026401000000009004565b50505050620003b4565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614151515620001ab57600080fd5b620001d081600254620002e36401000000000262001155179091906401000000009004565b60028190555062000237816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054620002e36401000000000262001155179091906401000000009004565b6000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a35050565b6000808284019050838110151515620002fb57600080fd5b8091505092915050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106200034857805160ff191683800117855562000379565b8280016001018555821562000379579182015b82811115620003785782518255916020019190600101906200035b565b5b5090506200038891906200038c565b5090565b620003b191905b80821115620003ad57600081600090555060010162000393565b5090565b90565b6111a280620003c46000396000f3fe6080604052600436106100af576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100b4578063095ea7b31461014457806318160ddd146101b757806323b872dd146101e2578063313ce5671461027557806339509351146102a657806370a082311461031957806395d89b411461037e578063a457c2d71461040e578063a9059cbb14610481578063dd62ed3e146104f4575b600080fd5b3480156100c057600080fd5b506100c9610579565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101095780820151818401526020810190506100ee565b50505050905090810190601f1680156101365780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561015057600080fd5b5061019d6004803603604081101561016757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061061b565b604051808215151515815260200191505060405180910390f35b3480156101c357600080fd5b506101cc610748565b6040518082815260200191505060405180910390f35b3480156101ee57600080fd5b5061025b6004803603606081101561020557600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610752565b604051808215151515815260200191505060405180910390f35b34801561028157600080fd5b5061028a61095a565b604051808260ff1660ff16815260200191505060405180910390f35b3480156102b257600080fd5b506102ff600480360360408110156102c957600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610971565b604051808215151515815260200191505060405180910390f35b34801561032557600080fd5b506103686004803603602081101561033c57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610ba8565b6040518082815260200191505060405180910390f35b34801561038a57600080fd5b50610393610bf0565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156103d35780820151818401526020810190506103b8565b50505050905090810190601f1680156104005780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561041a57600080fd5b506104676004803603604081101561043157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610c92565b604051808215151515815260200191505060405180910390f35b34801561048d57600080fd5b506104da600480360360408110156104a457600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610ec9565b604051808215151515815260200191505060405180910390f35b34801561050057600080fd5b506105636004803603604081101561051757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610ee0565b6040518082815260200191505060405180910390f35b606060038054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156106115780601f106105e657610100808354040283529160200191610611565b820191906000526020600020905b8154815290600101906020018083116105f457829003601f168201915b5050505050905090565b60008073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415151561065857600080fd5b81600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b6000600254905090565b60006107e382600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610f6790919063ffffffff16565b600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555061086e848484610f89565b3373ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925600160008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546040518082815260200191505060405180910390a3600190509392505050565b6000600560009054906101000a900460ff16905090565b60008073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141515156109ae57600080fd5b610a3d82600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205461115590919063ffffffff16565b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546040518082815260200191505060405180910390a36001905092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b606060048054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610c885780601f10610c5d57610100808354040283529160200191610c88565b820191906000526020600020905b815481529060010190602001808311610c6b57829003601f168201915b5050505050905090565b60008073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614151515610ccf57600080fd5b610d5e82600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610f6790919063ffffffff16565b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546040518082815260200191505060405180910390a36001905092915050565b6000610ed6338484610f89565b6001905092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b6000828211151515610f7857600080fd5b600082840390508091505092915050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614151515610fc557600080fd5b611016816000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610f6790919063ffffffff16565b6000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055506110a9816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205461115590919063ffffffff16565b6000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a3505050565b600080828401905083811015151561116c57600080fd5b809150509291505056fea165627a7a723058205fcdfea06f4d97b442bc9f444b1e92524bc66398eb4f37ed5a99f2093a8842640029000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000186a00000000000000000000000000000000000000000000000000000000000000003545354000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035453540000000000000000000000000000000000000000000000000000000000', - gas: '4700000', - gasPrice: '20000000000', - }, (error, contract) => { - if (error) { - tokenAddress.innerHTML = 'Creation Failed' - throw error - } else if (contract.address === undefined) { - return - } - + if (typeof contract.address !== 'undefined') { console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash) - tokenAddress.innerHTML = contract.address - transferTokens.disabled = false - approveTokens.disabled = false - transferTokensWithoutGas.disabled = false - approveTokensWithoutGas.disabled = false - transferTokens.onclick = (event) => { + document.getElementById('contractStatus').innerHTML = 'Deployed' + + depositButton.addEventListener('click', function () { + document.getElementById('contractStatus').innerHTML = 'Deposit initiated' + contract.deposit({ from: web3.eth.accounts[0], value: '0x3782dace9d900000' }, function (result) { + console.log(result) + document.getElementById('contractStatus').innerHTML = 'Deposit completed' + }) + }) + + withdrawButton.addEventListener('click', function () { + contract.withdraw('0xde0b6b3a7640000', { from: web3.eth.accounts[0] }, function (result) { + console.log(result) + document.getElementById('contractStatus').innerHTML = 'Withdrawn' + }) + }) + } + }) + + console.log(piggybank) + }) + + sendButton.addEventListener('click', function () { + web3.eth.sendTransaction({ + from: web3.eth.accounts[0], + to: '0x2f318C334780961FB129D2a6c30D0763d9a5C970', + value: '0x29a2241af62c0000', + gas: 21000, + gasPrice: 20000000000, + }, (result) => { + console.log(result) + }) + }) + + + createToken.addEventListener('click', async function () { + var _initialAmount = 100 + var _tokenName = 'TST' + var _decimalUnits = 0 + var _tokenSymbol = 'TST' + var humanstandardtokenContract = web3.eth.contract([{'constant': true, 'inputs': [], 'name': 'name', 'outputs': [{'name': '', 'type': 'string'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [{'name': '_spender', 'type': 'address'}, {'name': '_value', 'type': 'uint256'}], 'name': 'approve', 'outputs': [{'name': 'success', 'type': 'bool'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'totalSupply', 'outputs': [{'name': '', 'type': 'uint256'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [{'name': '_from', 'type': 'address'}, {'name': '_to', 'type': 'address'}, {'name': '_value', 'type': 'uint256'}], 'name': 'transferFrom', 'outputs': [{'name': 'success', 'type': 'bool'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'decimals', 'outputs': [{'name': '', 'type': 'uint8'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'version', 'outputs': [{'name': '', 'type': 'string'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': true, 'inputs': [{'name': '_owner', 'type': 'address'}], 'name': 'balanceOf', 'outputs': [{'name': 'balance', 'type': 'uint256'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'symbol', 'outputs': [{'name': '', 'type': 'string'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [{'name': '_to', 'type': 'address'}, {'name': '_value', 'type': 'uint256'}], 'name': 'transfer', 'outputs': [{'name': 'success', 'type': 'bool'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': false, 'inputs': [{'name': '_spender', 'type': 'address'}, {'name': '_value', 'type': 'uint256'}, {'name': '_extraData', 'type': 'bytes'}], 'name': 'approveAndCall', 'outputs': [{'name': 'success', 'type': 'bool'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [{'name': '_owner', 'type': 'address'}, {'name': '_spender', 'type': 'address'}], 'name': 'allowance', 'outputs': [{'name': 'remaining', 'type': 'uint256'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'inputs': [{'name': '_initialAmount', 'type': 'uint256'}, {'name': '_tokenName', 'type': 'string'}, {'name': '_decimalUnits', 'type': 'uint8'}, {'name': '_tokenSymbol', 'type': 'string'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'constructor'}, {'payable': false, 'stateMutability': 'nonpayable', 'type': 'fallback'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': '_from', 'type': 'address'}, {'indexed': true, 'name': '_to', 'type': 'address'}, {'indexed': false, 'name': '_value', 'type': 'uint256'}], 'name': 'Transfer', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': '_owner', 'type': 'address'}, {'indexed': true, 'name': '_spender', 'type': 'address'}, {'indexed': false, 'name': '_value', 'type': 'uint256'}], 'name': 'Approval', 'type': 'event'}]) + return humanstandardtokenContract.new( + _initialAmount, + _tokenName, + _decimalUnits, + _tokenSymbol, + { + from: web3.eth.accounts[0], + data: '0x60806040523480156200001157600080fd5b506040516200156638038062001566833981018060405260808110156200003757600080fd5b8101908080516401000000008111156200005057600080fd5b828101905060208101848111156200006757600080fd5b81518560018202830111640100000000821117156200008557600080fd5b50509291906020018051640100000000811115620000a257600080fd5b82810190506020810184811115620000b957600080fd5b8151856001820283011164010000000082111715620000d757600080fd5b5050929190602001805190602001909291908051906020019092919050505083838382600390805190602001906200011192919062000305565b5081600490805190602001906200012a92919062000305565b5080600560006101000a81548160ff021916908360ff1602179055505050506200016433826200016e640100000000026401000000009004565b50505050620003b4565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614151515620001ab57600080fd5b620001d081600254620002e36401000000000262001155179091906401000000009004565b60028190555062000237816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054620002e36401000000000262001155179091906401000000009004565b6000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a35050565b6000808284019050838110151515620002fb57600080fd5b8091505092915050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106200034857805160ff191683800117855562000379565b8280016001018555821562000379579182015b82811115620003785782518255916020019190600101906200035b565b5b5090506200038891906200038c565b5090565b620003b191905b80821115620003ad57600081600090555060010162000393565b5090565b90565b6111a280620003c46000396000f3fe6080604052600436106100af576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100b4578063095ea7b31461014457806318160ddd146101b757806323b872dd146101e2578063313ce5671461027557806339509351146102a657806370a082311461031957806395d89b411461037e578063a457c2d71461040e578063a9059cbb14610481578063dd62ed3e146104f4575b600080fd5b3480156100c057600080fd5b506100c9610579565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101095780820151818401526020810190506100ee565b50505050905090810190601f1680156101365780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561015057600080fd5b5061019d6004803603604081101561016757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061061b565b604051808215151515815260200191505060405180910390f35b3480156101c357600080fd5b506101cc610748565b6040518082815260200191505060405180910390f35b3480156101ee57600080fd5b5061025b6004803603606081101561020557600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610752565b604051808215151515815260200191505060405180910390f35b34801561028157600080fd5b5061028a61095a565b604051808260ff1660ff16815260200191505060405180910390f35b3480156102b257600080fd5b506102ff600480360360408110156102c957600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610971565b604051808215151515815260200191505060405180910390f35b34801561032557600080fd5b506103686004803603602081101561033c57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610ba8565b6040518082815260200191505060405180910390f35b34801561038a57600080fd5b50610393610bf0565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156103d35780820151818401526020810190506103b8565b50505050905090810190601f1680156104005780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561041a57600080fd5b506104676004803603604081101561043157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610c92565b604051808215151515815260200191505060405180910390f35b34801561048d57600080fd5b506104da600480360360408110156104a457600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610ec9565b604051808215151515815260200191505060405180910390f35b34801561050057600080fd5b506105636004803603604081101561051757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610ee0565b6040518082815260200191505060405180910390f35b606060038054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156106115780601f106105e657610100808354040283529160200191610611565b820191906000526020600020905b8154815290600101906020018083116105f457829003601f168201915b5050505050905090565b60008073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415151561065857600080fd5b81600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b6000600254905090565b60006107e382600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610f6790919063ffffffff16565b600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555061086e848484610f89565b3373ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925600160008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546040518082815260200191505060405180910390a3600190509392505050565b6000600560009054906101000a900460ff16905090565b60008073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141515156109ae57600080fd5b610a3d82600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205461115590919063ffffffff16565b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546040518082815260200191505060405180910390a36001905092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b606060048054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610c885780601f10610c5d57610100808354040283529160200191610c88565b820191906000526020600020905b815481529060010190602001808311610c6b57829003601f168201915b5050505050905090565b60008073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614151515610ccf57600080fd5b610d5e82600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610f6790919063ffffffff16565b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546040518082815260200191505060405180910390a36001905092915050565b6000610ed6338484610f89565b6001905092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b6000828211151515610f7857600080fd5b600082840390508091505092915050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614151515610fc557600080fd5b611016816000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610f6790919063ffffffff16565b6000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055506110a9816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205461115590919063ffffffff16565b6000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a3505050565b600080828401905083811015151561116c57600080fd5b809150509291505056fea165627a7a723058205fcdfea06f4d97b442bc9f444b1e92524bc66398eb4f37ed5a99f2093a8842640029000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000186a00000000000000000000000000000000000000000000000000000000000000003545354000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035453540000000000000000000000000000000000000000000000000000000000', + gas: '4700000', + gasPrice: '20000000000', + }, function (e, contract) { + console.log(e, contract) + if (typeof contract.address !== 'undefined') { + console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash) + + document.getElementById('tokenAddress').innerHTML = contract.address + + transferTokens.addEventListener('click', function (event) { console.log(`event`, event) contract.transfer('0x2f318C334780961FB129D2a6c30D0763d9a5C970', '15000', { - from: accounts[0], + from: web3.eth.accounts[0], to: contract.address, data: '0xa9059cbb0000000000000000000000002f318c334780961fb129d2a6c30d0763d9a5c9700000000000000000000000000000000000000000000000000000000000003a98', gas: 60000, gasPrice: '20000000000', - }, (result) => { + }, function (result) { console.log('result', result) }) - } + }) - approveTokens.onclick = () => { + approveTokens.addEventListener('click', function () { contract.approve('0x9bc5baF874d2DA8D216aE9f137804184EE5AfEF4', '70000', { - from: accounts[0], + from: web3.eth.accounts[0], to: contract.address, data: '0x095ea7b30000000000000000000000009bc5baF874d2DA8D216aE9f137804184EE5AfEF40000000000000000000000000000000000000000000000000000000000000005', gas: 60000, gasPrice: '20000000000', - }, (result) => { + }, function (result) { console.log(result) }) - } + }) - transferTokensWithoutGas.onclick = (event) => { + transferTokensWithoutGas.addEventListener('click', function (event) { console.log(`event`, event) contract.transfer('0x2f318C334780961FB129D2a6c30D0763d9a5C970', '15000', { - from: accounts[0], + from: web3.eth.accounts[0], to: contract.address, data: '0xa9059cbb0000000000000000000000002f318c334780961fb129d2a6c30d0763d9a5c9700000000000000000000000000000000000000000000000000000000000003a98', gasPrice: '20000000000', - }, (result) => { + }, function (result) { console.log('result', result) }) - } + }) - approveTokensWithoutGas.onclick = () => { + approveTokensWithoutGas.addEventListener('click', function () { contract.approve('0x2f318C334780961FB129D2a6c30D0763d9a5C970', '70000', { - from: accounts[0], + from: web3.eth.accounts[0], to: contract.address, data: '0x095ea7b30000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C9700000000000000000000000000000000000000000000000000000000000000005', gasPrice: '20000000000', - }, (result) => { + }, function (result) { console.log(result) }) - } - } - ) - } - - signTypedData.addEventListener('click', () => { - const typedData = { - types: { - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'version', type: 'string' }, - { name: 'chainId', type: 'uint256' }, - { name: 'verifyingContract', type: 'address' }, - ], - Person: [ - { name: 'name', type: 'string' }, - { name: 'wallet', type: 'address' }, - ], - Mail: [ - { name: 'from', type: 'Person' }, - { name: 'to', type: 'Person' }, - { name: 'contents', type: 'string' }, - ], - }, - primaryType: 'Mail', - domain: { - name: 'Ether Mail', - version: '1', - chainId: 3, - verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', - }, - message: { - sender: { - name: 'Cow', - wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', - }, - recipient: { - name: 'Bob', - wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', - }, - contents: 'Hello, Bob!', - }, - } - web3.currentProvider.sendAsync({ - method: 'eth_signTypedData_v3', - params: [ethereum.selectedAddress, JSON.stringify(typedData)], - from: ethereum.selectedAddress, - }, (err, result) => { - if (err) { - console.log(err) - } else { - signTypedDataResults.innerHTML = result + }) } }) - }) - getAccountsButton.addEventListener('click', async () => { - try { - const accounts = await ethereum.send({ method: 'eth_accounts' }) - getAccountsResults.innerHTML = accounts[0] || 'Not able to get accounts' - } catch (error) { - console.error(error) - getAccountsResults.innerHTML = `Error: ${error}` + }) + + ethereum.autoRefreshOnNetworkChange = false + + const networkDiv = document.getElementById('network') + const chainIdDiv = document.getElementById('chainId') + const accountsDiv = document.getElementById('accounts') + + ethereum.on('networkChanged', (networkId) => { + networkDiv.innerHTML = networkId + }) + + ethereum.on('chainIdChanged', (chainId) => { + chainIdDiv.innerHTML = chainId + }) + + ethereum.on('accountsChanged', (accounts) => { + accountsDiv.innerHTML = accounts + }) + + const signTypedDataResultsDiv = document.getElementById('signTypedDataResult') + signTypedData.addEventListener('click', function () { + + const typedData = { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], + }, + primaryType: 'Mail', + domain: { + name: 'Ether Mail', + version: '1', + chainId: 3, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + }, + message: { + sender: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + recipient: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, + } + web3.currentProvider.sendAsync({ + method: 'eth_signTypedData_v3', + params: [ethereum.selectedAddress, JSON.stringify(typedData)], + from: ethereum.selectedAddress, + }, function (err, result) { + if (err) { + console.log(err) + } else { + signTypedDataResultsDiv.innerHTML = result } }) - - } - - updateButtons() - if (isMetaMaskInstalled()) { - ethereum.autoRefreshOnNetworkChange = false - ethereum.on('networkChanged', (networkId) => { - networkDiv.innerHTML = networkId - }) - ethereum.on('chainIdChanged', (chainId) => { - chainIdDiv.innerHTML = chainId - }) - ethereum.on('accountsChanged', (newAccounts) => { - const connecting = Boolean((!accounts || !accounts.length) && newAccounts && newAccounts.length) - accounts = newAccounts - accountsDiv.innerHTML = accounts - if (connecting) { - initializeAccountButtons() - } - updateButtons() - }) - } -} -window.addEventListener('DOMContentLoaded', initialize) + }) +}) diff --git a/test/e2e/contract-test/index.html b/test/e2e/contract-test/index.html index 7655b8786..9689654ee 100644 --- a/test/e2e/contract-test/index.html +++ b/test/e2e/contract-test/index.html @@ -1,69 +1,51 @@ - E2E Test Dapp - - -
-

E2E Test Dapp

-
-
-
-

Connect

- -
-
-

Contract

-
- - - -
-
- Contract Status: Not clicked -
-
-
-

Send Eth

+
+
Contract
+
+ + + +
+
+ Not clicked +
+
+
+
Send eth
+
-
-
-

Send Tokens

-
- Token: -
-
- - - - - -
-
-
-

Get Accounts

- -
-
-
-

Status

-
- Network: -
-
- ChainId: -
-
- Accounts: -
-
-
-

Sign Typed Data

+ + +
+
Send tokens
+
+
+ + + + + +
+
+
+
Network:
+
ChainId:
+
Accounts:
+
+
+
+
Sign Typed Data
+
-
Sign Typed Data Result:
-
-
+
Sign Typed Data Result:
+ + + + - + + \ No newline at end of file diff --git a/test/e2e/ethereum-on.spec.js b/test/e2e/ethereum-on.spec.js index c50615521..9c6cd4ceb 100644 --- a/test/e2e/ethereum-on.spec.js +++ b/test/e2e/ethereum-on.spec.js @@ -113,42 +113,28 @@ describe('MetaMask', function () { let extension let popup let dapp - - it('connects to the dapp', async () => { + it('switches to a dapp', async () => { await openNewPage(driver, 'http://127.0.0.1:8080/') await delay(regularDelayMs) - const connectButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`)) - await connectButton.click() - - await delay(regularDelayMs) - await waitUntilXWindowHandles(driver, 3) const windowHandles = await driver.getAllWindowHandles() extension = windowHandles[0] - dapp = await switchToWindowWithTitle(driver, 'E2E Test Dapp', windowHandles) - popup = windowHandles.find(handle => handle !== extension && handle !== dapp) - - await driver.switchTo().window(popup) + popup = await switchToWindowWithTitle(driver, 'MetaMask Notification', windowHandles) + 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() - const accountButton = await findElement(driver, By.css('.permissions-connect-choose-account__account')) - await accountButton.click() - - const submitButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Submit')]`)) - await submitButton.click() - - await waitUntilXWindowHandles(driver, 2) await driver.switchTo().window(dapp) await delay(regularDelayMs) }) - it('has the ganache network id within the dapp', async () => { + it('has not set the network within the dapp', async () => { const networkDiv = await findElement(driver, By.css('#network')) - await delay(regularDelayMs) - assert.equal(await networkDiv.getText(), '5777') + assert.equal(await networkDiv.getText(), '') }) it('changes the network', async () => { diff --git a/test/e2e/helpers.js b/test/e2e/helpers.js index 27de882eb..9e12ba8da 100644 --- a/test/e2e/helpers.js +++ b/test/e2e/helpers.js @@ -123,8 +123,6 @@ async function loadExtension (driver, extensionId) { await driver.get(`moz-extension://${extensionId}/home.html`) break } - default: - throw new Error('Unrecognized SELENIUM_BROWSER value') } } diff --git a/test/e2e/metamask-responsive-ui.spec.js b/test/e2e/metamask-responsive-ui.spec.js index 143e759ee..90cf35710 100644 --- a/test/e2e/metamask-responsive-ui.spec.js +++ b/test/e2e/metamask-responsive-ui.spec.js @@ -134,7 +134,7 @@ describe('MetaMask', function () { it('show account details dropdown menu', async () => { await driver.findElement(By.css('div.menu-bar__open-in-browser')).click() const options = await driver.findElements(By.css('div.menu.account-details-dropdown div.menu__item')) - assert.equal(options.length, 4) // HD Wallet type does not have to show the Remove Account option + assert.equal(options.length, 3) // HD Wallet type does not have to show the Remove Account option await delay(regularDelayMs) }) }) diff --git a/test/e2e/metamask-ui.spec.js b/test/e2e/metamask-ui.spec.js index a22c0c1ca..a0efe56f9 100644 --- a/test/e2e/metamask-ui.spec.js +++ b/test/e2e/metamask-ui.spec.js @@ -432,35 +432,23 @@ describe('MetaMask', function () { await delay(largeDelayMs) }) - it('connects the dapp', async () => { + it('starts a send transaction inside the dapp', async () => { await openNewPage(driver, 'http://127.0.0.1:8080/') await delay(regularDelayMs) - const connectButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`)) - await connectButton.click() - - await delay(regularDelayMs) - await waitUntilXWindowHandles(driver, 3) windowHandles = await driver.getAllWindowHandles() extension = windowHandles[0] - dapp = await switchToWindowWithTitle(driver, 'E2E Test Dapp', windowHandles) - popup = windowHandles.find(handle => handle !== extension && handle !== dapp) - - await driver.switchTo().window(popup) + popup = await switchToWindowWithTitle(driver, 'MetaMask Notification', windowHandles) + 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() - const accountButton = await findElement(driver, By.css('.permissions-connect-choose-account__account')) - await accountButton.click() - - const submitButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Submit')]`)) - await submitButton.click() - - await waitUntilXWindowHandles(driver, 2) await driver.switchTo().window(dapp) - await delay(regularDelayMs) + await delay(2000) }) it('initiates a send from the dapp', async () => { diff --git a/test/e2e/permissions.spec.js b/test/e2e/permissions.spec.js deleted file mode 100644 index b7147d7a2..000000000 --- a/test/e2e/permissions.spec.js +++ /dev/null @@ -1,201 +0,0 @@ -const assert = require('assert') -const webdriver = require('selenium-webdriver') -const { By, until } = webdriver -const { - delay, -} = require('./func') -const { - checkBrowserForConsoleErrors, - findElement, - findElements, - openNewPage, - verboseReportOnFailure, - waitUntilXWindowHandles, - switchToWindowWithTitle, - setupFetchMocking, - prepareExtensionForTesting, -} = require('./helpers') -const enLocaleMessages = require('../../app/_locales/en/messages.json') - -describe('MetaMask', function () { - let driver - let publicAddress - - const tinyDelayMs = 200 - const regularDelayMs = tinyDelayMs * 2 - const largeDelayMs = regularDelayMs * 2 - - this.timeout(0) - this.bail(true) - - before(async function () { - const result = await prepareExtensionForTesting() - driver = result.driver - await setupFetchMocking(driver) - }) - - 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('Going through the first time flow, but skipping the seed phrase challenge', () => { - it('clicks the continue button on the welcome screen', async () => { - await findElement(driver, By.css('.welcome-page__header')) - const welcomeScreenBtn = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`)) - welcomeScreenBtn.click() - await delay(largeDelayMs) - }) - - it('clicks the "Create New Wallet" option', async () => { - const customRpcButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Create a 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('accepts a secure password', async () => { - const passwordBox = await findElement(driver, By.css('.first-time-flow__form #create-password')) - const passwordBoxConfirm = await findElement(driver, By.css('.first-time-flow__form #confirm-password')) - const button = await findElement(driver, By.css('.first-time-flow__form button')) - - await passwordBox.sendKeys('correct horse battery staple') - await passwordBoxConfirm.sendKeys('correct horse battery staple') - - const tosCheckBox = await findElement(driver, By.css('.first-time-flow__checkbox')) - await tosCheckBox.click() - - await button.click() - await delay(largeDelayMs) - }) - - it('skips the seed phrase challenge', async () => { - const button = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.remindMeLater.message}')]`)) - await button.click() - await delay(regularDelayMs) - - const detailsButton = await findElement(driver, By.css('.account-details__details-button')) - await detailsButton.click() - await delay(regularDelayMs) - }) - - it('gets the current accounts address', async () => { - const addressInput = await findElement(driver, By.css('.qr-ellip-address')) - publicAddress = await addressInput.getAttribute('value') - const accountModal = await driver.findElement(By.css('span .modal')) - - await driver.executeScript("document.querySelector('.account-modal-close').click()") - - await driver.wait(until.stalenessOf(accountModal)) - await delay(regularDelayMs) - }) - }) - - describe('sets permissions', () => { - let extension - let popup - let dapp - - it('connects to the dapp', async () => { - await openNewPage(driver, 'http://127.0.0.1:8080/') - await delay(regularDelayMs) - - const connectButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`)) - await connectButton.click() - - await waitUntilXWindowHandles(driver, 3) - const windowHandles = await driver.getAllWindowHandles() - - extension = windowHandles[0] - dapp = await switchToWindowWithTitle(driver, 'E2E Test Dapp', windowHandles) - popup = windowHandles.find(handle => handle !== extension && handle !== dapp) - - await driver.switchTo().window(popup) - - await delay(regularDelayMs) - - const accountButton = await findElement(driver, By.css('.permissions-connect-choose-account__account')) - await accountButton.click() - - const submitButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Submit')]`)) - await submitButton.click() - - await waitUntilXWindowHandles(driver, 2) - await driver.switchTo().window(extension) - await delay(regularDelayMs) - }) - - it('shows connected sites', async () => { - const connectedSites = await findElement(driver, By.xpath(`//button[contains(text(), 'Connected Sites')]`)) - await connectedSites.click() - - await findElement(driver, By.css('.connected-sites__title')) - - const domains = await findElements(driver, By.css('.connected-sites-list__domain')) - assert.equal(domains.length, 1) - - const domainName = await findElement(driver, By.css('.connected-sites-list__domain-name')) - assert.equal(await domainName.getText(), 'E2E Test Dapp') - - await domains[0].click() - - const permissionDescription = await findElement(driver, By.css('.connected-sites-list__permission-description')) - assert.equal(await permissionDescription.getText(), 'View the address of the selected account') - }) - - it('can get accounts within the dapp', async () => { - await driver.switchTo().window(dapp) - await delay(regularDelayMs) - - const getAccountsButton = await findElement(driver, By.xpath(`//button[contains(text(), 'eth_accounts')]`)) - await getAccountsButton.click() - - const getAccountsResult = await findElement(driver, By.css('#getAccountsResult')) - assert.equal((await getAccountsResult.getText()).toLowerCase(), publicAddress.toLowerCase()) - }) - - it('can disconnect all accounts', async () => { - await driver.switchTo().window(extension) - - const disconnectAllButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Disconnect All')]`)) - await disconnectAllButton.click() - - const disconnectModal = await driver.findElement(By.css('span .modal')) - - const disconnectAllModalButton = await findElement(driver, By.css('.disconnect-all-modal .btn-danger')) - await disconnectAllModalButton.click() - - await driver.wait(until.stalenessOf(disconnectModal)) - await delay(regularDelayMs) - }) - - it('can no longer get accounts within the dapp', async () => { - await driver.switchTo().window(dapp) - await delay(regularDelayMs) - - const getAccountsButton = await findElement(driver, By.xpath(`//button[contains(text(), 'eth_accounts')]`)) - await getAccountsButton.click() - - const getAccountsResult = await findElement(driver, By.css('#getAccountsResult')) - assert.equal(await getAccountsResult.getText(), 'Not able to get accounts') - }) - }) -}) diff --git a/test/e2e/run-all.sh b/test/e2e/run-all.sh index 55a06fc88..33c2428da 100755 --- a/test/e2e/run-all.sh +++ b/test/e2e/run-all.sh @@ -60,14 +60,6 @@ concurrently --kill-others \ 'yarn dapp' \ 'sleep 5 && mocha test/e2e/ethereum-on.spec' -concurrently --kill-others \ - --names 'ganache,dapp,e2e' \ - --prefix '[{time}][{name}]' \ - --success first \ - 'yarn ganache:start' \ - 'yarn dapp' \ - 'sleep 5 && mocha test/e2e/permissions.spec' - export GANACHE_ARGS="${BASE_GANACHE_ARGS} --deterministic --account=0x250F458997A364988956409A164BA4E16F0F99F916ACDD73ADCD3A1DE30CF8D1,0 --account=0x53CB0AB5226EEBF4D872113D98332C1555DC304443BEE1CF759D15798D3C55A9,25000000000000000000" concurrently --kill-others \ --names 'ganache,sendwithprivatedapp,e2e' \ diff --git a/test/e2e/run-web3.sh b/test/e2e/run-web3.sh index 729333b84..174370683 100755 --- a/test/e2e/run-web3.sh +++ b/test/e2e/run-web3.sh @@ -9,5 +9,5 @@ export PATH="$PATH:./node_modules/.bin" concurrently --kill-others \ --names 'dapp,e2e' \ --prefix '[{time}][{name}]' \ - 'node development/static-server.js test/web3 --port 8080' \ + 'static-server test/web3 --port 8080' \ 'sleep 5 && mocha test/e2e/web3.spec' diff --git a/test/e2e/signature-request.spec.js b/test/e2e/signature-request.spec.js index f36b6ac51..e9490e08d 100644 --- a/test/e2e/signature-request.spec.js +++ b/test/e2e/signature-request.spec.js @@ -119,40 +119,28 @@ describe('MetaMask', function () { }) }) - describe('successfuly signs typed data', () => { + describe('provider listening for events', () => { let extension let popup let dapp let windowHandles - - it('connects to the dapp', async () => { + it('switches to a dapp', async () => { await openNewPage(driver, 'http://127.0.0.1:8080/') await delay(regularDelayMs) - const connectButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`)) - await connectButton.click() - - await delay(regularDelayMs) - await waitUntilXWindowHandles(driver, 3) - const windowHandles = await driver.getAllWindowHandles() + windowHandles = await driver.getAllWindowHandles() extension = windowHandles[0] - dapp = await switchToWindowWithTitle(driver, 'E2E Test Dapp', windowHandles) - popup = windowHandles.find(handle => handle !== extension && handle !== dapp) - - await driver.switchTo().window(popup) + popup = await switchToWindowWithTitle(driver, 'MetaMask Notification', windowHandles) + 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() - const accountButton = await findElement(driver, By.css('.permissions-connect-choose-account__account')) - await accountButton.click() - - const submitButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Submit')]`)) - await submitButton.click() - - await waitUntilXWindowHandles(driver, 2) await driver.switchTo().window(dapp) + await delay(regularDelayMs) }) it('creates a sign typed data signature request', async () => { @@ -160,7 +148,6 @@ describe('MetaMask', function () { await signTypedMessage.click() await delay(largeDelayMs) - await delay(regularDelayMs) windowHandles = await driver.getAllWindowHandles() await switchToWindowWithTitle(driver, 'MetaMask Notification', windowHandles) await delay(regularDelayMs) diff --git a/test/e2e/web3.spec.js b/test/e2e/web3.spec.js index 8c0853755..6dd081322 100644 --- a/test/e2e/web3.spec.js +++ b/test/e2e/web3.spec.js @@ -30,7 +30,7 @@ describe('Using MetaMask with an existing account', function () { await delay(largeDelayMs) const [results] = await findElements(driver, By.css('#results')) const resulttext = await results.getText() - const parsedData = JSON.parse(resulttext) + var parsedData = JSON.parse(resulttext) return (parsedData) @@ -126,11 +126,6 @@ describe('Using MetaMask with an existing account', function () { await openNewPage(driver, 'http://127.0.0.1:8080/') await delay(regularDelayMs) - const connectButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`)) - await connectButton.click() - - await delay(regularDelayMs) - await waitUntilXWindowHandles(driver, 3) const windowHandles = await driver.getAllWindowHandles() @@ -153,14 +148,14 @@ describe('Using MetaMask with an existing account', function () { it('testing hexa methods', async () => { - const List = await driver.findElements(By.className('hexaNumberMethods')) + var List = await driver.findElements(By.className('hexaNumberMethods')) for (let i = 0; i < List.length; i++) { try { - const parsedData = await button(List[i]) + var parsedData = await button(List[i]) console.log(parsedData) - const result = parseInt(parsedData.result, 16) + var result = parseInt(parsedData.result, 16) assert.equal((typeof result === 'number'), true) await delay(regularDelayMs) @@ -174,14 +169,14 @@ describe('Using MetaMask with an existing account', function () { it('testing booleanMethods', async () => { - const List = await driver.findElements(By.className('booleanMethods')) + var List = await driver.findElements(By.className('booleanMethods')) for (let i = 0; i < List.length; i++) { try { - const parsedData = await button(List[i]) + var parsedData = await button(List[i]) console.log(parsedData) - const result = parsedData.result + var result = parsedData.result assert.equal(result, false) await delay(regularDelayMs) @@ -197,16 +192,16 @@ describe('Using MetaMask with an existing account', function () { it('testing transactionMethods', async () => { - const List = await driver.findElements(By.className('transactionMethods')) + var List = await driver.findElements(By.className('transactionMethods')) for (let i = 0; i < List.length; i++) { try { - const parsedData = await button(List[i]) + var parsedData = await button(List[i]) console.log(parsedData.result.blockHash) - const result = [] + var result = [] result.push(parseInt(parsedData.result.blockHash, 16)) result.push(parseInt(parsedData.result.blockNumber, 16)) result.push(parseInt(parsedData.result.gas, 16)) @@ -239,17 +234,17 @@ describe('Using MetaMask with an existing account', function () { it('testing blockMethods', async () => { - const List = await driver.findElements(By.className('blockMethods')) + var List = await driver.findElements(By.className('blockMethods')) for (let i = 0; i < List.length; i++) { try { - const parsedData = await button(List[i]) + var parsedData = await button(List[i]) console.log(JSON.stringify(parsedData) + i) console.log(parsedData.result.parentHash) - const result = parseInt(parsedData.result.parentHash, 16) + var result = parseInt(parsedData.result.parentHash, 16) assert.equal((typeof result === 'number'), true) await delay(regularDelayMs) @@ -265,9 +260,9 @@ describe('Using MetaMask with an existing account', function () { it('testing methods', async () => { - const List = await driver.findElements(By.className('methods')) - let parsedData - let result + var List = await driver.findElements(By.className('methods')) + var parsedData + var result for (let i = 0; i < List.length; i++) { try { diff --git a/test/helper.js b/test/helper.js index bea0b326c..ddc2aba40 100644 --- a/test/helper.js +++ b/test/helper.js @@ -17,7 +17,7 @@ server.listen(8545, () => { }) // logging util -const log = require('loglevel') +var log = require('loglevel') log.setDefaultLevel(5) global.log = log @@ -36,12 +36,8 @@ require('jsdom-global')() window.localStorage = {} // crypto.getRandomValues -if (!window.crypto) { - window.crypto = {} -} -if (!window.crypto.getRandomValues) { - window.crypto.getRandomValues = require('polyfill-crypto.getrandomvalues') -} +if (!window.crypto) window.crypto = {} +if (!window.crypto.getRandomValues) window.crypto.getRandomValues = require('polyfill-crypto.getrandomvalues') function enableFailureOnUnhandledPromiseRejection () { // overwrite node's promise with the stricter Bluebird promise @@ -62,11 +58,9 @@ function enableFailureOnUnhandledPromiseRejection () { throw evt.detail.reason }) } else { - const oldOHR = window.onunhandledrejection + var oldOHR = window.onunhandledrejection window.onunhandledrejection = function (evt) { - if (typeof oldOHR === 'function') { - oldOHR.apply(this, arguments) - } + if (typeof oldOHR === 'function') oldOHR.apply(this, arguments) throw evt.detail.reason } } diff --git a/test/integration/index.js b/test/integration/index.js index 1c4523e1e..b266ddf37 100644 --- a/test/integration/index.js +++ b/test/integration/index.js @@ -19,9 +19,7 @@ pump( b.bundle(), writeStream, (err) => { - if (err) { - throw err - } + if (err) throw err console.log(`Integration test build completed: "${bundlePath}"`) process.exit(0) } diff --git a/test/lib/mock-encryptor.js b/test/lib/mock-encryptor.js index 0f70b5b3c..23ab2404f 100644 --- a/test/lib/mock-encryptor.js +++ b/test/lib/mock-encryptor.js @@ -1,5 +1,5 @@ -const mockHex = '0xabcdef0123456789' -const mockKey = Buffer.alloc(32) +var mockHex = '0xabcdef0123456789' +var mockKey = Buffer.alloc(32) let cacheVal module.exports = { diff --git a/test/lib/mock-simple-keychain.js b/test/lib/mock-simple-keychain.js index 74e6fd8a2..d3addc3e8 100644 --- a/test/lib/mock-simple-keychain.js +++ b/test/lib/mock-simple-keychain.js @@ -1,4 +1,4 @@ -const fakeWallet = { +var fakeWallet = { privKey: '0x123456788890abcdef', address: '0xfedcba0987654321', } @@ -6,9 +6,7 @@ const type = 'Simple Key Pair' module.exports = class MockSimpleKeychain { - static type () { - return type - } + static type () { return type } constructor (opts) { this.type = type @@ -28,7 +26,7 @@ module.exports = class MockSimpleKeychain { } addAccounts (n = 1) { - for (let i = 0; i < n; i++) { + for (var i = 0; i < n; i++) { this.wallets.push(fakeWallet) } } diff --git a/test/lib/react-trigger-change.js b/test/lib/react-trigger-change.js index 52fe779c1..d169dd614 100644 --- a/test/lib/react-trigger-change.js +++ b/test/lib/react-trigger-change.js @@ -18,7 +18,7 @@ // Constants and functions are declared inside the closure. // In this way, reactTriggerChange can be passed directly to executeScript in Selenium. module.exports = function reactTriggerChange (node) { - const supportedInputTypes = { + var supportedInputTypes = { color: true, date: true, datetime: true, @@ -35,27 +35,27 @@ module.exports = function reactTriggerChange (node) { url: true, week: true, } - const nodeName = node.nodeName.toLowerCase() - const type = node.type - let event - let descriptor - let initialValue - let initialChecked - let initialCheckedRadio + var nodeName = node.nodeName.toLowerCase() + var type = node.type + var event + var descriptor + var initialValue + var initialChecked + var initialCheckedRadio // Do not try to delete non-configurable properties. // Value and checked properties on DOM elements are non-configurable in PhantomJS. function deletePropertySafe (elem, prop) { - const desc = Object.getOwnPropertyDescriptor(elem, prop) + var desc = Object.getOwnPropertyDescriptor(elem, prop) if (desc && desc.configurable) { delete elem[prop] } } function getCheckedRadio (radio) { - const name = radio.name - let radios - let i + var name = radio.name + var radios + var i if (name) { radios = document.querySelectorAll('input[type="radio"][name="' + name + '"]') for (i = 0; i < radios.length; i += 1) { diff --git a/test/lib/util.js b/test/lib/util.js index be7e240a4..4c5d789d1 100644 --- a/test/lib/util.js +++ b/test/lib/util.js @@ -15,9 +15,7 @@ async function findAsync (container, selector, opts) { try { return await pollUntilTruthy(() => { const result = container.find(selector) - if (result.length > 0) { - return result - } + if (result.length > 0) return result }, opts) } catch (err) { throw new Error(`Failed to find element within interval: "${selector}"`) @@ -28,9 +26,7 @@ async function queryAsync (jQuery, selector, opts) { try { return await pollUntilTruthy(() => { const result = jQuery(selector) - if (result.length > 0) { - return result - } + if (result.length > 0) return result }, opts) } catch (err) { throw new Error(`Failed to find element within interval: "${selector}"`) diff --git a/test/setup.js b/test/setup.js index 5aa6e59dd..bccb4d58e 100644 --- a/test/setup.js +++ b/test/setup.js @@ -5,10 +5,6 @@ require('@babel/register')({ require('./helper') window.SVGPathElement = window.SVGPathElement || { prototype: {} } -window.fetch = window.fetch || function fetch () { - return Promise.resolve() -} +window.fetch = window.fetch || function fetch () { return Promise.resolve() } global.indexedDB = {} -global.fetch = global.fetch || function fetch () { - return Promise.resolve() -} +global.fetch = global.fetch || function fetch () { return Promise.resolve() } diff --git a/test/unit/actions/config_test.js b/test/unit/actions/config_test.js index 39224a9b1..9127474a8 100644 --- a/test/unit/actions/config_test.js +++ b/test/unit/actions/config_test.js @@ -1,13 +1,13 @@ // var jsdom = require('mocha-jsdom') -const assert = require('assert') -const freeze = require('deep-freeze-strict') -const path = require('path') +var assert = require('assert') +var freeze = require('deep-freeze-strict') +var path = require('path') -const actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'store', 'actions.js')) -const reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'ducks', 'index.js')) +var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'store', 'actions.js')) +var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'ducks', 'index.js')) describe('config view actions', function () { - const initialState = { + var initialState = { metamask: { rpcTarget: 'foo', frequentRpcList: [], @@ -22,7 +22,7 @@ describe('config view actions', function () { describe('SHOW_CONFIG_PAGE', function () { it('should set appState.currentView.name to config', function () { - const result = reducers(initialState, actions.showConfigPage()) + var result = reducers(initialState, actions.showConfigPage()) assert.equal(result.appState.currentView.name, 'config') }) }) @@ -34,7 +34,7 @@ describe('config view actions', function () { value: 'foo', } - const result = reducers(initialState, action) + var result = reducers(initialState, action) assert.equal(result.metamask.provider.type, 'rpc') assert.equal(result.metamask.provider.rpcTarget, 'foo') }) diff --git a/test/unit/actions/set_selected_account_test.js b/test/unit/actions/set_selected_account_test.js index 0488c844e..36d312d7b 100644 --- a/test/unit/actions/set_selected_account_test.js +++ b/test/unit/actions/set_selected_account_test.js @@ -1,14 +1,14 @@ // var jsdom = require('mocha-jsdom') -const assert = require('assert') -const freeze = require('deep-freeze-strict') -const path = require('path') +var assert = require('assert') +var freeze = require('deep-freeze-strict') +var path = require('path') -const actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'store', 'actions.js')) -const reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'ducks', 'index.js')) +var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'store', 'actions.js')) +var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'ducks', 'index.js')) describe('SET_SELECTED_ACCOUNT', function () { it('sets the state.appState.activeAddress property of the state to the action.value', function () { - const initialState = { + var initialState = { appState: { activeAddress: 'foo', }, @@ -21,14 +21,14 @@ describe('SET_SELECTED_ACCOUNT', function () { } freeze(action) - const resultingState = reducers(initialState, action) + var resultingState = reducers(initialState, action) assert.equal(resultingState.appState.activeAddress, action.value) }) }) describe('SHOW_ACCOUNT_DETAIL', function () { it('updates metamask state', function () { - const initialState = { + var initialState = { metamask: { selectedAddress: 'foo', }, @@ -41,7 +41,7 @@ describe('SHOW_ACCOUNT_DETAIL', function () { } freeze(action) - const resultingState = reducers(initialState, action) + var resultingState = reducers(initialState, action) assert.equal(resultingState.metamask.selectedAddress, action.value) }) }) diff --git a/test/unit/actions/tx_test.js b/test/unit/actions/tx_test.js index 0b2da16aa..66378b594 100644 --- a/test/unit/actions/tx_test.js +++ b/test/unit/actions/tx_test.js @@ -1,5 +1,5 @@ -const assert = require('assert') -const path = require('path') +var assert = require('assert') +var path = require('path') import configureMockStore from 'redux-mock-store' import thunk from 'redux-thunk' @@ -33,15 +33,9 @@ describe('tx confirmation screen', function () { describe('cancelTx', function () { before(function (done) { actions._setBackgroundConnection({ - approveTransaction (_, cb) { - cb('An error!') - }, - cancelTransaction (_, cb) { - cb() - }, - getState (cb) { - cb() - }, + approveTransaction (_, cb) { cb('An error!') }, + cancelTransaction (_, cb) { cb() }, + getState (cb) { cb() }, }) done() }) diff --git a/test/unit/actions/view_info_test.js b/test/unit/actions/view_info_test.js index 4792af727..5785a368c 100644 --- a/test/unit/actions/view_info_test.js +++ b/test/unit/actions/view_info_test.js @@ -1,14 +1,14 @@ // var jsdom = require('mocha-jsdom') -const assert = require('assert') -const freeze = require('deep-freeze-strict') -const path = require('path') +var assert = require('assert') +var freeze = require('deep-freeze-strict') +var path = require('path') -const actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'store', 'actions.js')) -const reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'ducks', 'index.js')) +var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'store', 'actions.js')) +var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'ducks', 'index.js')) describe('SHOW_INFO_PAGE', function () { it('sets the state.appState.currentView.name property to info', function () { - const initialState = { + var initialState = { appState: { activeAddress: 'foo', }, @@ -16,7 +16,7 @@ describe('SHOW_INFO_PAGE', function () { freeze(initialState) const action = actions.showInfoPage() - const resultingState = reducers(initialState, action) + var resultingState = reducers(initialState, action) assert.equal(resultingState.appState.currentView.name, 'info') }) }) diff --git a/test/unit/actions/warning_test.js b/test/unit/actions/warning_test.js index ac639f600..e57374cda 100644 --- a/test/unit/actions/warning_test.js +++ b/test/unit/actions/warning_test.js @@ -1,14 +1,14 @@ // var jsdom = require('mocha-jsdom') -const assert = require('assert') -const freeze = require('deep-freeze-strict') -const path = require('path') +var assert = require('assert') +var freeze = require('deep-freeze-strict') +var path = require('path') -const actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'store', 'actions.js')) -const reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'ducks', 'index.js')) +var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'store', 'actions.js')) +var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'ducks', 'index.js')) describe('action DISPLAY_WARNING', function () { it('sets appState.warning to provided value', function () { - const initialState = { + var initialState = { appState: {}, } freeze(initialState) diff --git a/test/unit/app/buy-eth-url.spec.js b/test/unit/app/buy-eth-url.spec.js index 96814c59d..7db992244 100644 --- a/test/unit/app/buy-eth-url.spec.js +++ b/test/unit/app/buy-eth-url.spec.js @@ -17,10 +17,10 @@ describe('buy-eth-url', function () { network: '42', } - it('returns wyre url with address for network 1', function () { + it('returns coinbase url with amount and address for network 1', function () { const wyreUrl = getBuyEthUrl(mainnet) - assert.equal(wyreUrl, 'https://pay.sendwyre.com/?dest=ethereum:0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc&destCurrency=ETH&accountId=AC-7AG3W4XH4N2') + assert.equal(wyreUrl, 'https://dash.sendwyre.com/sign-up') }) diff --git a/test/unit/app/controllers/detect-tokens-test.js b/test/unit/app/controllers/detect-tokens-test.js index c65ee97ab..8f18406f4 100644 --- a/test/unit/app/controllers/detect-tokens-test.js +++ b/test/unit/app/controllers/detect-tokens-test.js @@ -54,7 +54,7 @@ describe('DetectTokensController', () => { controller.isOpen = true controller.isUnlocked = true - const stub = sandbox.stub(controller, 'detectNewTokens') + var stub = sandbox.stub(controller, 'detectNewTokens') clock.tick(1) sandbox.assert.notCalled(stub) @@ -70,7 +70,7 @@ describe('DetectTokensController', () => { controller.isOpen = true controller.isUnlocked = true - const stub = sandbox.stub(controller, 'detectTokenBalance') + var stub = sandbox.stub(controller, 'detectTokenBalance') .withArgs('0x0D262e5dC4A06a0F1c90cE79C7a60C09DfC884E4').returns(true) .withArgs('0xBC86727E770de68B1060C91f6BB6945c73e10388').returns(true) @@ -114,7 +114,7 @@ describe('DetectTokensController', () => { it('should trigger detect new tokens when change address', async () => { controller.isOpen = true controller.isUnlocked = true - const stub = sandbox.stub(controller, 'detectNewTokens') + var stub = sandbox.stub(controller, 'detectNewTokens') await preferences.setSelectedAddress('0xbc86727e770de68b1060c91f6bb6945c73e10388') sandbox.assert.called(stub) }) @@ -122,7 +122,7 @@ describe('DetectTokensController', () => { it('should trigger detect new tokens when submit password', async () => { controller.isOpen = true controller.selectedAddress = '0x0' - const stub = sandbox.stub(controller, 'detectNewTokens') + var stub = sandbox.stub(controller, 'detectNewTokens') await controller._keyringMemStore.updateState({ isUnlocked: true }) sandbox.assert.called(stub) }) @@ -130,7 +130,7 @@ describe('DetectTokensController', () => { it('should not trigger detect new tokens when not open or not unlocked', async () => { controller.isOpen = true controller.isUnlocked = false - const stub = sandbox.stub(controller, 'detectTokenBalance') + var stub = sandbox.stub(controller, 'detectTokenBalance') clock.tick(180000) sandbox.assert.notCalled(stub) controller.isOpen = false diff --git a/test/unit/app/controllers/metamask-controller-test.js b/test/unit/app/controllers/metamask-controller-test.js index a904dec08..7eac91a9b 100644 --- a/test/unit/app/controllers/metamask-controller-test.js +++ b/test/unit/app/controllers/metamask-controller-test.js @@ -227,9 +227,7 @@ describe('MetaMaskController', function () { it('should be able to call newVaultAndRestore despite a mistake.', async function () { const password = 'what-what-what' sandbox.stub(metamaskController, 'getBalance') - metamaskController.getBalance.callsFake(() => { - return Promise.resolve('0x0') - }) + metamaskController.getBalance.callsFake(() => { return Promise.resolve('0x0') }) await metamaskController.createNewVaultAndRestore(password, TEST_SEED.slice(0, -1)).catch(() => null) await metamaskController.createNewVaultAndRestore(password, TEST_SEED) @@ -239,9 +237,7 @@ describe('MetaMaskController', function () { it('should clear previous identities after vault restoration', async () => { sandbox.stub(metamaskController, 'getBalance') - metamaskController.getBalance.callsFake(() => { - return Promise.resolve('0x0') - }) + metamaskController.getBalance.callsFake(() => { return Promise.resolve('0x0') }) await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED) assert.deepEqual(metamaskController.getState().identities, { @@ -680,9 +676,7 @@ describe('MetaMaskController', function () { beforeEach(async () => { sandbox.stub(metamaskController, 'getBalance') - metamaskController.getBalance.callsFake(() => { - return Promise.resolve('0x0') - }) + metamaskController.getBalance.callsFake(() => { return Promise.resolve('0x0') }) await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED_ALT) @@ -740,9 +734,7 @@ describe('MetaMaskController', function () { beforeEach(async function () { sandbox.stub(metamaskController, 'getBalance') - metamaskController.getBalance.callsFake(() => { - return Promise.resolve('0x0') - }) + metamaskController.getBalance.callsFake(() => { return Promise.resolve('0x0') }) await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED_ALT) @@ -817,9 +809,7 @@ describe('MetaMaskController', function () { const { promise, resolve } = deferredPromise() streamTest = createThoughStream((chunk, _, cb) => { - if (chunk.name !== 'phishing') { - return cb() - } + if (chunk.name !== 'phishing') return cb() assert.equal(chunk.data.hostname, phishingUrl.hostname) resolve() cb() @@ -939,8 +929,6 @@ describe('MetaMaskController', function () { function deferredPromise () { let resolve - const promise = new Promise(_resolve => { - resolve = _resolve - }) + const promise = new Promise(_resolve => { resolve = _resolve }) return { promise, resolve } } diff --git a/test/unit/app/controllers/network/pending-middleware-test.js b/test/unit/app/controllers/network/pending-middleware-test.js index ac6f8ad9a..838395b0b 100644 --- a/test/unit/app/controllers/network/pending-middleware-test.js +++ b/test/unit/app/controllers/network/pending-middleware-test.js @@ -19,9 +19,7 @@ describe('#createPendingNonceMiddleware', function () { it('should fill the result with a the "pending" nonce', (done) => { const req = { method: 'eth_getTransactionCount', params: [address, 'pending'] } const res = {} - pendingNonceMiddleware(req, res, () => { - done(new Error('should not have called next')) - }, () => { + pendingNonceMiddleware(req, res, () => { done(new Error('should not have called next')) }, () => { assert(res.result === '0x2') done() }) @@ -65,9 +63,7 @@ describe('#createPendingTxMiddleware', function () { returnUndefined = false const req = { method: 'eth_getTransactionByHash', params: [address, 'pending'] } const res = {} - pendingTxMiddleware(req, res, () => { - done(new Error('should not have called next')) - }, () => { + pendingTxMiddleware(req, res, () => { done(new Error('should not have called next')) }, () => { /* // uncomment this section for debugging help with non matching keys const coppy = {...res.result} diff --git a/test/unit/app/controllers/preferences-controller-test.js b/test/unit/app/controllers/preferences-controller-test.js index 7aea39a36..7e047a2a8 100644 --- a/test/unit/app/controllers/preferences-controller-test.js +++ b/test/unit/app/controllers/preferences-controller-test.js @@ -1,7 +1,6 @@ const assert = require('assert') const ObservableStore = require('obs-store') const PreferencesController = require('../../../../app/scripts/controllers/preferences') -const { addInternalMethodPrefix } = require('../../../../app/scripts/controllers/permissions') const sinon = require('sinon') describe('preferences controller', function () { @@ -343,7 +342,7 @@ describe('preferences controller', function () { }) describe('on watchAsset', function () { - let stubNext, stubEnd, stubHandleWatchAssetERC20, asy, req, res + var stubNext, stubEnd, stubHandleWatchAssetERC20, asy, req, res const sandbox = sinon.createSandbox() beforeEach(() => { @@ -360,8 +359,8 @@ describe('preferences controller', function () { it('shouldn not do anything if method not corresponds', async function () { const asy = {next: () => {}, end: () => {}} - const stubNext = sandbox.stub(asy, 'next') - const stubEnd = sandbox.stub(asy, 'end').returns(0) + var stubNext = sandbox.stub(asy, 'next') + var stubEnd = sandbox.stub(asy, 'end').returns(0) req.method = 'metamask' await preferencesController.requestWatchAsset(req, res, asy.next, asy.end) sandbox.assert.notCalled(stubEnd) @@ -369,14 +368,14 @@ describe('preferences controller', function () { }) it('should do something if method is supported', async function () { const asy = {next: () => {}, end: () => {}} - const stubNext = sandbox.stub(asy, 'next') - const stubEnd = sandbox.stub(asy, 'end').returns(0) + var stubNext = sandbox.stub(asy, 'next') + var stubEnd = sandbox.stub(asy, 'end').returns(0) req.method = 'metamask_watchAsset' req.params.type = 'someasset' await preferencesController.requestWatchAsset(req, res, asy.next, asy.end) sandbox.assert.called(stubEnd) sandbox.assert.notCalled(stubNext) - req.method = addInternalMethodPrefix('watchAsset') + req.method = 'wallet_watchAsset' req.params.type = 'someasset' await preferencesController.requestWatchAsset(req, res, asy.next, asy.end) sandbox.assert.calledTwice(stubEnd) @@ -401,7 +400,7 @@ describe('preferences controller', function () { }) describe('on watchAsset of type ERC20', function () { - let req + var req const sandbox = sinon.createSandbox() beforeEach(() => { @@ -456,44 +455,28 @@ describe('preferences controller', function () { }) it('should validate ERC20 asset correctly', async function () { const validateSpy = sandbox.spy(preferencesController._validateERC20AssetParams) - try { - validateSpy({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABC', decimals: 0}) - } catch (e) {} + try { validateSpy({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABC', decimals: 0}) } catch (e) {} assert.equal(validateSpy.threw(), false, 'correct options object') const validateSpyAddress = sandbox.spy(preferencesController._validateERC20AssetParams) - try { - validateSpyAddress({symbol: 'ABC', decimals: 0}) - } catch (e) {} + try { validateSpyAddress({symbol: 'ABC', decimals: 0}) } catch (e) {} assert.equal(validateSpyAddress.threw(), true, 'options object with no address') const validateSpySymbol = sandbox.spy(preferencesController._validateERC20AssetParams) - try { - validateSpySymbol({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', decimals: 0}) - } catch (e) {} + try { validateSpySymbol({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', decimals: 0}) } catch (e) {} assert.equal(validateSpySymbol.threw(), true, 'options object with no symbol') const validateSpyDecimals = sandbox.spy(preferencesController._validateERC20AssetParams) - try { - validateSpyDecimals({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABC'}) - } catch (e) {} + try { validateSpyDecimals({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABC'}) } catch (e) {} assert.equal(validateSpyDecimals.threw(), true, 'options object with no decimals') const validateSpyInvalidSymbol = sandbox.spy(preferencesController._validateERC20AssetParams) - try { - validateSpyInvalidSymbol({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABCDEFGHI', decimals: 0}) - } catch (e) {} + try { validateSpyInvalidSymbol({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABCDEFGHI', decimals: 0}) } catch (e) {} assert.equal(validateSpyInvalidSymbol.threw(), true, 'options object with invalid symbol') const validateSpyInvalidDecimals1 = sandbox.spy(preferencesController._validateERC20AssetParams) - try { - validateSpyInvalidDecimals1({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABCDEFGHI', decimals: -1}) - } catch (e) {} + try { validateSpyInvalidDecimals1({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABCDEFGHI', decimals: -1}) } catch (e) {} assert.equal(validateSpyInvalidDecimals1.threw(), true, 'options object with decimals less than zero') const validateSpyInvalidDecimals2 = sandbox.spy(preferencesController._validateERC20AssetParams) - try { - validateSpyInvalidDecimals2({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABCDEFGHI', decimals: 38}) - } catch (e) {} + try { validateSpyInvalidDecimals2({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABCDEFGHI', decimals: 38}) } catch (e) {} assert.equal(validateSpyInvalidDecimals2.threw(), true, 'options object with decimals more than 36') const validateSpyInvalidAddress = sandbox.spy(preferencesController._validateERC20AssetParams) - try { - validateSpyInvalidAddress({rawAddress: '0x123', symbol: 'ABC', decimals: 0}) - } catch (e) {} + try { validateSpyInvalidAddress({rawAddress: '0x123', symbol: 'ABC', decimals: 0}) } catch (e) {} assert.equal(validateSpyInvalidAddress.threw(), true, 'options object with address invalid') }) }) diff --git a/test/unit/app/controllers/provider-approval-test.js b/test/unit/app/controllers/provider-approval-test.js new file mode 100644 index 000000000..eeb9e813b --- /dev/null +++ b/test/unit/app/controllers/provider-approval-test.js @@ -0,0 +1,330 @@ +const assert = require('assert') +const sinon = require('sinon') +const ProviderApprovalController = require('../../../../app/scripts/controllers/provider-approval') + +const mockLockedKeyringController = { + memStore: { + getState: () => ({ + isUnlocked: false, + }), + }, +} + +const mockUnlockedKeyringController = { + memStore: { + getState: () => ({ + isUnlocked: true, + }), + }, +} + +describe('ProviderApprovalController', () => { + describe('#_handleProviderRequest', () => { + it('should add a pending provider request when unlocked', () => { + const controller = new ProviderApprovalController({ + keyringController: mockUnlockedKeyringController, + }) + + const metadata = { + hostname: 'https://example.com', + origin: 'example.com', + siteTitle: 'Example', + siteImage: 'https://example.com/logo.svg', + } + + controller._handleProviderRequest(metadata) + assert.deepEqual(controller._getMergedState(), { + approvedOrigins: {}, + providerRequests: [metadata], + }) + }) + + it('should add a pending provider request when locked', () => { + const controller = new ProviderApprovalController({ + keyringController: mockLockedKeyringController, + }) + + const metadata = { + hostname: 'https://example.com', + origin: 'example.com', + siteTitle: 'Example', + siteImage: 'https://example.com/logo.svg', + } + controller._handleProviderRequest(metadata) + assert.deepEqual(controller._getMergedState(), { + approvedOrigins: {}, + providerRequests: [metadata], + }) + }) + + it('should add a 2nd pending provider request when unlocked', () => { + const controller = new ProviderApprovalController({ + keyringController: mockUnlockedKeyringController, + }) + + const metadata = [{ + hostname: 'https://example1.com', + origin: 'example1.com', + siteTitle: 'Example 1', + siteImage: 'https://example1.com/logo.svg', + }, { + hostname: 'https://example2.com', + origin: 'example2.com', + siteTitle: 'Example 2', + siteImage: 'https://example2.com/logo.svg', + }] + + controller._handleProviderRequest(metadata[0]) + controller._handleProviderRequest(metadata[1]) + assert.deepEqual(controller._getMergedState(), { + approvedOrigins: {}, + providerRequests: metadata, + }) + }) + + it('should add a 2nd pending provider request when locked', () => { + const controller = new ProviderApprovalController({ + keyringController: mockLockedKeyringController, + }) + + const metadata = [{ + hostname: 'https://example1.com', + origin: 'example1.com', + siteTitle: 'Example 1', + siteImage: 'https://example1.com/logo.svg', + }, { + hostname: 'https://example2.com', + origin: 'example2.com', + siteTitle: 'Example 2', + siteImage: 'https://example2.com/logo.svg', + }] + + controller._handleProviderRequest(metadata[0]) + controller._handleProviderRequest(metadata[1]) + assert.deepEqual(controller._getMergedState(), { + approvedOrigins: {}, + providerRequests: metadata, + }) + }) + + it('should call openPopup when unlocked and when given', () => { + const openPopup = sinon.spy() + const controller = new ProviderApprovalController({ + openPopup, + keyringController: mockUnlockedKeyringController, + }) + + const metadata = { + hostname: 'https://example.com', + origin: 'example.com', + siteTitle: 'Example', + siteImage: 'https://example.com/logo.svg', + } + controller._handleProviderRequest(metadata) + assert.ok(openPopup.calledOnce) + }) + + it('should call openPopup when locked and when given', () => { + const openPopup = sinon.spy() + const controller = new ProviderApprovalController({ + openPopup, + keyringController: mockLockedKeyringController, + }) + + const metadata = { + hostname: 'https://example.com', + origin: 'example.com', + siteTitle: 'Example', + siteImage: 'https://example.com/logo.svg', + } + controller._handleProviderRequest(metadata) + assert.ok(openPopup.calledOnce) + }) + + it('should NOT call openPopup when unlocked and when the domain has already been approved', () => { + const openPopup = sinon.spy() + const controller = new ProviderApprovalController({ + openPopup, + keyringController: mockUnlockedKeyringController, + }) + + controller.store.updateState({ + approvedOrigins: { + 'example.com': { + siteTitle: 'Example', + siteImage: 'https://example.com/logo.svg', + }, + }, + }) + const metadata = { + hostname: 'https://example.com', + origin: 'example.com', + siteTitle: 'Example', + siteImage: 'https://example.com/logo.svg', + } + controller._handleProviderRequest(metadata) + assert.ok(openPopup.notCalled) + }) + }) + + describe('#approveProviderRequestByOrigin', () => { + it('should mark the origin as approved and remove the provider request', () => { + const controller = new ProviderApprovalController({ + keyringController: mockUnlockedKeyringController, + }) + + const metadata = { + hostname: 'https://example.com', + origin: 'example.com', + siteTitle: 'Example', + siteImage: 'https://example.com/logo.svg', + } + controller._handleProviderRequest(metadata) + controller.approveProviderRequestByOrigin('example.com') + assert.deepEqual(controller._getMergedState(), { + providerRequests: [], + approvedOrigins: { + 'example.com': { + hostname: 'https://example.com', + siteTitle: 'Example', + siteImage: 'https://example.com/logo.svg', + }, + }, + }) + }) + + it('should mark the origin as approved and multiple requests for the same domain', () => { + const controller = new ProviderApprovalController({ + keyringController: mockUnlockedKeyringController, + }) + + const metadata = { + hostname: 'https://example.com', + origin: 'example.com', + siteTitle: 'Example', + siteImage: 'https://example.com/logo.svg', + } + controller._handleProviderRequest(metadata) + controller._handleProviderRequest(metadata) + controller.approveProviderRequestByOrigin('example.com') + assert.deepEqual(controller._getMergedState(), { + providerRequests: [], + approvedOrigins: { + 'example.com': { + hostname: 'https://example.com', + siteTitle: 'Example', + siteImage: 'https://example.com/logo.svg', + }, + }, + }) + }) + + it('should mark the origin as approved without a provider request', () => { + const controller = new ProviderApprovalController({ + keyringController: mockUnlockedKeyringController, + }) + + controller.approveProviderRequestByOrigin('example.com') + assert.deepEqual(controller._getMergedState(), { + providerRequests: [], + approvedOrigins: { + 'example.com': { + hostname: null, + siteTitle: null, + siteImage: null, + }, + }, + }) + }) + }) + + describe('#rejectProviderRequestByOrigin', () => { + it('should remove the origin from approved', () => { + const controller = new ProviderApprovalController({ + keyringController: mockUnlockedKeyringController, + }) + + const metadata = { + hostname: 'https://example.com', + origin: 'example.com', + siteTitle: 'Example', + siteImage: 'https://example.com/logo.svg', + } + controller._handleProviderRequest(metadata) + controller.approveProviderRequestByOrigin('example.com') + controller.rejectProviderRequestByOrigin('example.com') + assert.deepEqual(controller._getMergedState(), { + providerRequests: [], + approvedOrigins: {}, + }) + }) + + it('should reject the origin even without a pending request', () => { + const controller = new ProviderApprovalController({ + keyringController: mockUnlockedKeyringController, + }) + + controller.rejectProviderRequestByOrigin('example.com') + assert.deepEqual(controller._getMergedState(), { + providerRequests: [], + approvedOrigins: {}, + }) + }) + }) + + describe('#clearApprovedOrigins', () => { + it('should clear the approved origins', () => { + const controller = new ProviderApprovalController({ + keyringController: mockUnlockedKeyringController, + }) + + const metadata = { + hostname: 'https://example.com', + origin: 'example.com', + siteTitle: 'Example', + siteImage: 'https://example.com/logo.svg', + } + controller._handleProviderRequest(metadata) + controller.approveProviderRequestByOrigin('example.com') + controller.clearApprovedOrigins() + assert.deepEqual(controller._getMergedState(), { + providerRequests: [], + approvedOrigins: {}, + }) + }) + }) + + describe('#shouldExposeAccounts', () => { + it('should return true for an approved origin', () => { + const controller = new ProviderApprovalController({ + keyringController: mockUnlockedKeyringController, + }) + + const metadata = { + hostname: 'https://example.com', + origin: 'example.com', + siteTitle: 'Example', + siteImage: 'https://example.com/logo.svg', + } + controller._handleProviderRequest(metadata) + controller.approveProviderRequestByOrigin('example.com') + assert.ok(controller.shouldExposeAccounts('example.com')) + }) + + it('should return false for an origin not yet approved', () => { + const controller = new ProviderApprovalController({ + keyringController: mockUnlockedKeyringController, + }) + + const metadata = { + hostname: 'https://example.com', + origin: 'example.com', + siteTitle: 'Example', + siteImage: 'https://example.com/logo.svg', + } + controller._handleProviderRequest(metadata) + controller.approveProviderRequestByOrigin('example.com') + assert.ok(!controller.shouldExposeAccounts('bad.website')) + }) + }) +}) diff --git a/test/unit/app/controllers/transactions/pending-tx-test.js b/test/unit/app/controllers/transactions/pending-tx-test.js index 622a53b18..e1de5731b 100644 --- a/test/unit/app/controllers/transactions/pending-tx-test.js +++ b/test/unit/app/controllers/transactions/pending-tx-test.js @@ -40,19 +40,13 @@ describe('PendingTransactionTracker', function () { return { releaseLock: () => {} } }, }, - getPendingTransactions: () => { - return [] - }, - getCompletedTransactions: () => { - return [] - }, + getPendingTransactions: () => { return [] }, + getCompletedTransactions: () => { return [] }, publishTransaction: () => {}, confirmTransaction: () => {}, }) - pendingTxTracker._getBlock = (blockNumber) => { - return {number: blockNumber, transactions: []} - } + pendingTxTracker._getBlock = (blockNumber) => { return {number: blockNumber, transactions: []} } }) describe('_checkPendingTx state management', function () { @@ -156,18 +150,14 @@ describe('PendingTransactionTracker', function () { txMeta2.id = 2 txMeta3.id = 3 txList = [txMeta, txMeta2, txMeta3].map((tx) => { - tx.processed = new Promise((resolve) => { - tx.resolve = resolve - }) + tx.processed = new Promise((resolve) => { tx.resolve = resolve }) return tx }) }) it('should warp all txMeta\'s in #updatePendingTxs', function (done) { pendingTxTracker.getPendingTransactions = () => txList - pendingTxTracker._checkPendingTx = (tx) => { - tx.resolve(tx) - } + pendingTxTracker._checkPendingTx = (tx) => { tx.resolve(tx) } Promise.all(txList.map((tx) => tx.processed)) .then(() => done()) .catch(done) @@ -181,9 +171,7 @@ describe('PendingTransactionTracker', function () { beforeEach(function () { const txMeta2 = txMeta3 = txMeta txList = [txMeta, txMeta2, txMeta3].map((tx) => { - tx.processed = new Promise((resolve) => { - tx.resolve = resolve - }) + tx.processed = new Promise((resolve) => { tx.resolve = resolve }) return tx }) }) @@ -193,9 +181,7 @@ describe('PendingTransactionTracker', function () { }) it('should call #_resubmitTx for all pending tx\'s', function (done) { pendingTxTracker.getPendingTransactions = () => txList - pendingTxTracker._resubmitTx = async (tx) => { - tx.resolve(tx) - } + pendingTxTracker._resubmitTx = async (tx) => { tx.resolve(tx) } Promise.all(txList.map((tx) => tx.processed)) .then(() => done()) .catch(done) @@ -239,9 +225,7 @@ describe('PendingTransactionTracker', function () { }) pendingTxTracker.getPendingTransactions = () => txList - pendingTxTracker._resubmitTx = async () => { - 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(() => done()) .catch(done) @@ -385,9 +369,7 @@ describe('PendingTransactionTracker', function () { rawTx: '0xf86c808504a817c800827b0d940c62bb85faa3311a998d3aba8098c1235c564966880de0b6b3a7640000802aa08ff665feb887a25d4099e40e11f0fef93ee9608f404bd3f853dd9e84ed3317a6a02ec9d3d1d6e176d4d2593dd760e74ccac753e6a0ea0d00cc9789d0d7ff1f471d', }] pendingTxTracker.getCompletedTransactions = (address) => { - if (!address) { - throw new Error('unless behavior has changed #_checkIfNonceIsTaken needs a filtered list of transactions to see if the nonce is taken') - } + if (!address) throw new Error('unless behavior has changed #_checkIfNonceIsTaken needs a filtered list of transactions to see if the nonce is taken') return confirmedTxList } }) diff --git a/test/unit/app/controllers/transactions/tx-controller-test.js b/test/unit/app/controllers/transactions/tx-controller-test.js index d398c7e04..76b8e5025 100644 --- a/test/unit/app/controllers/transactions/tx-controller-test.js +++ b/test/unit/app/controllers/transactions/tx-controller-test.js @@ -38,9 +38,7 @@ describe('Transaction Controller', function () { blockTrackerStub.getLatestBlock = noop txController = new TransactionController({ provider, - getGasPrice: function () { - return '0xee6b2800' - }, + getGasPrice: function () { return '0xee6b2800' }, networkStore: netStore, txHistoryLimit: 10, blockTracker: blockTrackerStub, @@ -48,7 +46,6 @@ describe('Transaction Controller', function () { ethTx.sign(fromAccount.key) resolve() }), - getPermittedAccounts: () => {}, }) txController.nonceTracker.getNonceLock = () => Promise.resolve({ nextNonce: 0, releaseLock: noop }) }) @@ -165,11 +162,8 @@ describe('Transaction Controller', function () { txController.newUnapprovedTransaction(txParams) .catch((err) => { - if (err.message === 'MetaMask Tx Signature: User denied transaction signature.') { - done() - } else { - done(err) - } + if (err.message === 'MetaMask Tx Signature: User denied transaction signature.') done() + else done(err) }) }) }) @@ -177,15 +171,13 @@ describe('Transaction Controller', function () { describe('#addUnapprovedTransaction', function () { const selectedAddress = '0x1678a085c290ebd122dc42cba69373b5953b831d' - let getSelectedAddress, getPermittedAccounts + let getSelectedAddress beforeEach(function () { getSelectedAddress = sinon.stub(txController, 'getSelectedAddress').returns(selectedAddress) - getPermittedAccounts = sinon.stub(txController, 'getPermittedAccounts').returns([selectedAddress]) }) afterEach(function () { getSelectedAddress.restore() - getPermittedAccounts.restore() }) it('should add an unapproved transaction and return a valid txMeta', function (done) { @@ -217,11 +209,8 @@ describe('Transaction Controller', function () { txController.networkStore = new ObservableStore(1) txController.addUnapprovedTransaction({ from: selectedAddress, to: '0x0d1d4e623D10F9FBA5Db95830F7d3839406C6AF2' }) .catch((err) => { - if (err.message === 'Recipient is a public account') { - done() - } else { - done(err) - } + if (err.message === 'Recipient is a public account') done() + else done(err) }) }) @@ -250,11 +239,8 @@ describe('Transaction Controller', function () { txController.networkStore = new ObservableStore('loading') txController.addUnapprovedTransaction({ from: selectedAddress, to: '0x0d1d4e623D10F9FBA5Db95830F7d3839406C6AF2' }) .catch((err) => { - if (err.message === 'MetaMask is having trouble connecting to the network') { - done() - } else { - done(err) - } + if (err.message === 'MetaMask is having trouble connecting to the network') done() + else done(err) }) }) }) @@ -417,9 +403,7 @@ describe('Transaction Controller', function () { assert.equal(status, 'rejected', 'status should e rejected') assert.equal(txId, 0, 'id should e 0') done() - } catch (e) { - done(e) - } + } catch (e) { done(e) } }) txController.cancelTransaction(0) @@ -514,9 +498,7 @@ describe('Transaction Controller', function () { }) it('should ignore the error "Transaction Failed: known transaction" and be as usual', async function () { - providerResultStub['eth_sendRawTransaction'] = async (_, __, ___, end) => { - end('Transaction Failed: known transaction') - } + providerResultStub['eth_sendRawTransaction'] = async (_, __, ___, end) => { end('Transaction Failed: known transaction') } const rawTx = '0xf86204831e848082520894f231d46dd78806e1dd93442cf33c7671f853874880802ca05f973e540f2d3c2f06d3725a626b75247593cb36477187ae07ecfe0a4db3cf57a00259b52ee8c58baaa385fb05c3f96116e58de89bcc165cb3bfdfc708672fed8a' txController.txStateManager.addTx(txMeta) await txController.publishTransaction(txMeta.id, rawTx) @@ -635,9 +617,7 @@ describe('Transaction Controller', function () { _blockTrackerStub.getLatestBlock = noop const _txController = new TransactionController({ provider: _provider, - getGasPrice: function () { - return '0xee6b2800' - }, + getGasPrice: function () { return '0xee6b2800' }, networkStore: new ObservableStore(currentNetworkId), txHistoryLimit: 10, blockTracker: _blockTrackerStub, @@ -667,9 +647,7 @@ describe('Transaction Controller', function () { _blockTrackerStub.getLatestBlock = noop const _txController = new TransactionController({ provider: _provider, - getGasPrice: function () { - return '0xee6b2800' - }, + getGasPrice: function () { return '0xee6b2800' }, networkStore: new ObservableStore(currentNetworkId), txHistoryLimit: 10, blockTracker: _blockTrackerStub, 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 6136de66e..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 @@ -106,9 +106,7 @@ describe('Transaction state history helper', function () { assert.equal(result[0].path, expectedEntry1.path) assert.equal(result[0].value, expectedEntry1.value) assert.equal(result[0].value, expectedEntry1.value) - if (note) { - assert.equal(result[0].note, note) - } + if (note) { assert.equal(result[0].note, note) } assert.ok(result[0].timestamp >= before && result[0].timestamp <= after) diff --git a/test/unit/app/controllers/transactions/tx-utils-test.js b/test/unit/app/controllers/transactions/tx-utils-test.js index e8bc8a5b7..65c8d35b0 100644 --- a/test/unit/app/controllers/transactions/tx-utils-test.js +++ b/test/unit/app/controllers/transactions/tx-utils-test.js @@ -5,7 +5,7 @@ const txUtils = require('../../../../../app/scripts/controllers/transactions/lib describe('txUtils', function () { describe('#validateTxParams', function () { it('does not throw for positive values', function () { - const sample = { + var sample = { from: '0x1678a085c290ebd122dc42cba69373b5953b831d', value: '0x01', } @@ -13,7 +13,7 @@ describe('txUtils', function () { }) it('returns error for negative values', function () { - const sample = { + var sample = { from: '0x1678a085c290ebd122dc42cba69373b5953b831d', value: '-0x01', } @@ -66,9 +66,7 @@ describe('txUtils', function () { from: '0x1678a085c290ebd122dc42cba69373b5953b831d', to: '0x', } - assert.throws(() => { - txUtils.validateRecipient(zeroRecipientTxParams) - }, Error, 'Invalid recipient address') + assert.throws(() => { txUtils.validateRecipient(zeroRecipientTxParams) }, Error, 'Invalid recipient address') }) }) @@ -78,27 +76,19 @@ describe('txUtils', function () { // where from is undefined const txParams = {} - assert.throws(() => { - txUtils.validateFrom(txParams) - }, Error, `Invalid from address ${txParams.from} not a string`) + assert.throws(() => { txUtils.validateFrom(txParams) }, Error, `Invalid from address ${txParams.from} not a string`) // where from is array txParams.from = [] - assert.throws(() => { - txUtils.validateFrom(txParams) - }, Error, `Invalid from address ${txParams.from} not a string`) + assert.throws(() => { txUtils.validateFrom(txParams) }, Error, `Invalid from address ${txParams.from} not a string`) // where from is a object txParams.from = {} - assert.throws(() => { - txUtils.validateFrom(txParams) - }, Error, `Invalid from address ${txParams.from} not a string`) + assert.throws(() => { txUtils.validateFrom(txParams) }, Error, `Invalid from address ${txParams.from} not a string`) // where from is a invalid address txParams.from = 'im going to fail' - assert.throws(() => { - txUtils.validateFrom(txParams) - }, Error, `Invalid from address`) + assert.throws(() => { txUtils.validateFrom(txParams) }, Error, `Invalid from address`) // should run txParams.from = '0x1678a085c290ebd122dc42cba69373b5953b831d' diff --git a/test/unit/app/edge-encryptor-test.js b/test/unit/app/edge-encryptor-test.js index 3f2e8830a..ad873e351 100644 --- a/test/unit/app/edge-encryptor-test.js +++ b/test/unit/app/edge-encryptor-test.js @@ -2,8 +2,8 @@ const assert = require('assert') const EdgeEncryptor = require('../../../app/scripts/edge-encryptor') -const password = 'passw0rd1' -const data = 'some random data' +var password = 'passw0rd1' +var data = 'some random data' global.crypto = global.crypto || { getRandomValues: function (array) { diff --git a/test/unit/app/message-manager-test.js b/test/unit/app/message-manager-test.js index b824acfe7..36ef6c29f 100644 --- a/test/unit/app/message-manager-test.js +++ b/test/unit/app/message-manager-test.js @@ -10,7 +10,7 @@ describe('Message Manager', function () { describe('#getMsgList', function () { it('when new should return empty array', function () { - const result = messageManager.messages + var result = messageManager.messages assert.ok(Array.isArray(result)) assert.equal(result.length, 0) }) @@ -21,9 +21,9 @@ describe('Message Manager', function () { describe('#addMsg', function () { it('adds a Msg returned in getMsgList', function () { - const Msg = { id: 1, status: 'approved', metamaskNetworkId: 'unit test' } + var Msg = { id: 1, status: 'approved', metamaskNetworkId: 'unit test' } messageManager.addMsg(Msg) - const result = messageManager.messages + var result = messageManager.messages assert.ok(Array.isArray(result)) assert.equal(result.length, 1) assert.equal(result[0].id, 1) @@ -32,10 +32,10 @@ describe('Message Manager', function () { describe('#setMsgStatusApproved', function () { it('sets the Msg status to approved', function () { - const Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } + var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } messageManager.addMsg(Msg) messageManager.setMsgStatusApproved(1) - const result = messageManager.messages + var result = messageManager.messages assert.ok(Array.isArray(result)) assert.equal(result.length, 1) assert.equal(result[0].status, 'approved') @@ -44,10 +44,10 @@ describe('Message Manager', function () { describe('#rejectMsg', function () { it('sets the Msg status to rejected', function () { - const Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } + var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } messageManager.addMsg(Msg) messageManager.rejectMsg(1) - const result = messageManager.messages + var result = messageManager.messages assert.ok(Array.isArray(result)) assert.equal(result.length, 1) assert.equal(result[0].status, 'rejected') @@ -59,7 +59,7 @@ describe('Message Manager', function () { messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }) messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' }) messageManager._updateMsg({ id: '1', status: 'blah', hash: 'foo', metamaskNetworkId: 'unit test' }) - const result = messageManager.getMsg('1') + var result = messageManager.getMsg('1') assert.equal(result.hash, 'foo') }) }) diff --git a/test/unit/app/nodeify-test.js b/test/unit/app/nodeify-test.js index fc06803db..fa5e49fb2 100644 --- a/test/unit/app/nodeify-test.js +++ b/test/unit/app/nodeify-test.js @@ -33,9 +33,7 @@ describe('nodeify', function () { }) it('no callback - should asyncly throw an error if underlying function does', function (done) { - const nodified = nodeify(async () => { - throw new Error('boom!') - }, obj) + const nodified = nodeify(async () => { throw new Error('boom!') }, obj) process.prependOnceListener('uncaughtException', function (err) { assert.ok(err, 'got expected error') assert.ok(err.message.includes('boom!'), 'got expected error message') @@ -52,9 +50,7 @@ describe('nodeify', function () { const nodified = nodeify(() => 42) try { nodified((err, result) => { - if (err) { - return done(new Error(`should not have thrown any error: ${err.message}`)) - } + if (err) return done(new Error(`should not have thrown any error: ${err.message}`)) assert.equal(42, result, 'got expected result') }) done() @@ -64,14 +60,10 @@ describe('nodeify', function () { }) it('sync functions - handles errors', function (done) { - const nodified = nodeify(() => { - throw new Error('boom!') - }) + const nodified = nodeify(() => { throw new Error('boom!') }) try { nodified((err, result) => { - if (result) { - return done(new Error('should not have returned any result')) - } + if (result) return done(new Error('should not have returned any result')) assert.ok(err, 'got expected error') assert.ok(err.message.includes('boom!'), 'got expected error message') }) diff --git a/test/unit/app/personal-message-manager-test.js b/test/unit/app/personal-message-manager-test.js index 52cbdb75c..b07167bff 100644 --- a/test/unit/app/personal-message-manager-test.js +++ b/test/unit/app/personal-message-manager-test.js @@ -11,7 +11,7 @@ describe('Personal Message Manager', function () { describe('#getMsgList', function () { it('when new should return empty array', function () { - const result = messageManager.messages + var result = messageManager.messages assert.ok(Array.isArray(result)) assert.equal(result.length, 0) }) @@ -22,9 +22,9 @@ describe('Personal Message Manager', function () { describe('#addMsg', function () { it('adds a Msg returned in getMsgList', function () { - const Msg = { id: 1, status: 'approved', metamaskNetworkId: 'unit test' } + var Msg = { id: 1, status: 'approved', metamaskNetworkId: 'unit test' } messageManager.addMsg(Msg) - const result = messageManager.messages + var result = messageManager.messages assert.ok(Array.isArray(result)) assert.equal(result.length, 1) assert.equal(result[0].id, 1) @@ -33,10 +33,10 @@ describe('Personal Message Manager', function () { describe('#setMsgStatusApproved', function () { it('sets the Msg status to approved', function () { - const Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } + var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } messageManager.addMsg(Msg) messageManager.setMsgStatusApproved(1) - const result = messageManager.messages + var result = messageManager.messages assert.ok(Array.isArray(result)) assert.equal(result.length, 1) assert.equal(result[0].status, 'approved') @@ -45,10 +45,10 @@ describe('Personal Message Manager', function () { describe('#rejectMsg', function () { it('sets the Msg status to rejected', function () { - const Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } + var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } messageManager.addMsg(Msg) messageManager.rejectMsg(1) - const result = messageManager.messages + var result = messageManager.messages assert.ok(Array.isArray(result)) assert.equal(result.length, 1) assert.equal(result[0].status, 'rejected') @@ -60,7 +60,7 @@ describe('Personal Message Manager', function () { messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }) messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' }) messageManager._updateMsg({ id: '1', status: 'blah', hash: 'foo', metamaskNetworkId: 'unit test' }) - const result = messageManager.getMsg('1') + var result = messageManager.getMsg('1') assert.equal(result.hash, 'foo') }) }) @@ -87,20 +87,20 @@ describe('Personal Message Manager', function () { describe('#normalizeMsgData', function () { it('converts text to a utf8 hex string', function () { - const input = 'hello' - const output = messageManager.normalizeMsgData(input) + var input = 'hello' + var output = messageManager.normalizeMsgData(input) assert.equal(output, '0x68656c6c6f', 'predictably hex encoded') }) it('tolerates a hex prefix', function () { - const input = '0x12' - const output = messageManager.normalizeMsgData(input) + var input = '0x12' + var output = messageManager.normalizeMsgData(input) assert.equal(output, '0x12', 'un modified') }) it('tolerates normal hex', function () { - const input = '12' - const output = messageManager.normalizeMsgData(input) + var input = '12' + var output = messageManager.normalizeMsgData(input) assert.equal(output, '0x12', 'adds prefix') }) }) diff --git a/test/unit/app/typed-message-manager.spec.js b/test/unit/app/typed-message-manager.spec.js index 959662b91..3db92d4a3 100644 --- a/test/unit/app/typed-message-manager.spec.js +++ b/test/unit/app/typed-message-manager.spec.js @@ -30,12 +30,12 @@ describe('Typed Message Manager', () => { 'EIP712Domain': [ {'name': 'name', 'type': 'string' }, {'name': 'version', 'type': 'string' }, - {'name': 'chainId', 'type': 'uint256' }, - {'name': 'verifyingContract', 'type': 'address' }, + {'name': 'chainId', ' type': 'uint256' }, + {'name': 'verifyingContract', ' type': 'address' }, ], 'Person': [ {'name': 'name', 'type': 'string' }, - {'name': 'wallet', 'type': 'address' }, + {'name': 'wallet', ' type': 'address' }, ], 'Mail': [ {'name': 'from', 'type': 'Person' }, @@ -64,7 +64,7 @@ describe('Typed Message Manager', () => { }), } - typedMessageManager.addUnapprovedMessage(msgParamsV3, null, 'V3') + typedMessageManager.addUnapprovedMessage(msgParamsV3, 'V3') typedMsgs = typedMessageManager.getUnapprovedMsgs() messages = typedMessageManager.messages msgId = Object.keys(typedMsgs)[0] @@ -73,7 +73,7 @@ describe('Typed Message Manager', () => { }) it('supports version 1 of signedTypedData', () => { - typedMessageManager.addUnapprovedMessage(msgParamsV1, null, 'V1') + typedMessageManager.addUnapprovedMessage(msgParamsV1, 'V1') assert.equal(messages[messages.length - 1].msgParams.data, msgParamsV1.data) }) @@ -87,7 +87,7 @@ describe('Typed Message Manager', () => { it('validates params', function () { assert.doesNotThrow(() => { - typedMessageManager.validateParams(messages[0].msgParams) + typedMessageManager.validateParams(messages[0]) }, 'Does not throw with valid parameters') }) diff --git a/test/unit/migrations/023-test.js b/test/unit/migrations/023-test.js index 7c93feefe..1b47dea92 100644 --- a/test/unit/migrations/023-test.js +++ b/test/unit/migrations/023-test.js @@ -37,9 +37,7 @@ let nonDeletableCount = 0 let status while (transactions.length <= 100) { status = txStates[Math.floor(Math.random() * Math.floor(txStates.length - 1))] - if (!deletableTxStates.find((s) => s === status)) { - nonDeletableCount++ - } + if (!deletableTxStates.find((s) => s === status)) nonDeletableCount++ transactions.push({status}) } diff --git a/test/unit/migrations/024-test.js b/test/unit/migrations/024-test.js index 9a6a6661a..671c7f832 100644 --- a/test/unit/migrations/024-test.js +++ b/test/unit/migrations/024-test.js @@ -31,11 +31,8 @@ describe('storage is migrated successfully and the txParams.from are lowercase', .then((migratedData) => { const migratedTransactions = migratedData.data.TransactionController.transactions migratedTransactions.forEach((tx) => { - if (tx.status === 'unapproved') { - assert.equal(tx.txParams.from, '0x8acce2391c0d510a6c5e5d8f819a678f79b7e675') - } else { - assert.equal(tx.txParams.from, '0x8aCce2391c0d510a6c5E5d8f819a678f79b7e675') - } + if (tx.status === 'unapproved') assert.equal(tx.txParams.from, '0x8acce2391c0d510a6c5e5d8f819a678f79b7e675') + else assert.equal(tx.txParams.from, '0x8aCce2391c0d510a6c5E5d8f819a678f79b7e675') }) done() }).catch(done) diff --git a/test/unit/migrations/025-test.js b/test/unit/migrations/025-test.js index cabaecd41..fa89bc54f 100644 --- a/test/unit/migrations/025-test.js +++ b/test/unit/migrations/025-test.js @@ -32,12 +32,8 @@ describe('storage is migrated successfully and the txParams.from are lowercase', .then((migratedData) => { const migratedTransactions = migratedData.data.TransactionController.transactions migratedTransactions.forEach((tx) => { - if (tx.status === 'unapproved') { - assert(!tx.txParams.random) - } - if (tx.status === 'unapproved') { - assert(!tx.txParams.chainId) - } + if (tx.status === 'unapproved') assert(!tx.txParams.random) + if (tx.status === 'unapproved') assert(!tx.txParams.chainId) }) done() }).catch(done) diff --git a/test/unit/migrations/027-test.js b/test/unit/migrations/027-test.js index 767243a25..3ec9f0c0e 100644 --- a/test/unit/migrations/027-test.js +++ b/test/unit/migrations/027-test.js @@ -30,9 +30,7 @@ describe('migration #27', () => { const newTransactions = newStorage.data.TransactionController.transactions assert.equal(newTransactions.length, 6, 'transactions is expected to have the length of 6') newTransactions.forEach((txMeta) => { - if (txMeta.status === 'rejected') { - done(new Error('transaction was found with a status of rejected')) - } + if (txMeta.status === 'rejected') done(new Error('transaction was found with a status of rejected')) }) done() }) diff --git a/test/unit/migrations/029-test.js b/test/unit/migrations/029-test.js index ca0996389..7f9b8a005 100644 --- a/test/unit/migrations/029-test.js +++ b/test/unit/migrations/029-test.js @@ -28,9 +28,7 @@ describe('storage is migrated successfully where transactions that are submitted assert(txMeta1.err.message.includes('too long'), 'error message assigned') txs.forEach((tx) => { - if (tx.id === 1) { - return - } + if (tx.id === 1) return assert.notEqual(tx.status, 'failed', 'other tx is not auto failed') }) diff --git a/test/unit/migrations/migrator-test.js b/test/unit/migrations/migrator-test.js index 9a949def4..3dcc5aff7 100644 --- a/test/unit/migrations/migrator-test.js +++ b/test/unit/migrations/migrator-test.js @@ -58,9 +58,7 @@ describe('Migrator', () => { it('should emit an error', function (done) { this.timeout(15000) - const migrator = new Migrator({ migrations: [{ version: 1, migrate: async () => { - throw new Error('test') - } } ] }) + const migrator = new Migrator({ migrations: [{ version: 1, migrate: async () => { throw new Error('test') } } ] }) migrator.on('error', () => done()) migrator.migrateData({ meta: {version: 0} }) .then(() => { diff --git a/test/unit/reducers/unlock_vault_test.js b/test/unit/reducers/unlock_vault_test.js index bb992ab40..d66891a63 100644 --- a/test/unit/reducers/unlock_vault_test.js +++ b/test/unit/reducers/unlock_vault_test.js @@ -1,11 +1,11 @@ // var jsdom = require('mocha-jsdom') -const assert = require('assert') +var assert = require('assert') // var freeze = require('deep-freeze-strict') -const path = require('path') -const sinon = require('sinon') +var path = require('path') +var sinon = require('sinon') -const actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'store', 'actions.js')) -const reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'ducks', 'index.js')) +var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'store', 'actions.js')) +var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'ducks', 'index.js')) describe('#unlockMetamask(selectedAccount)', function () { beforeEach(function () { diff --git a/test/unit/responsive/components/dropdown-test.js b/test/unit/responsive/components/dropdown-test.js index 2b9a5457b..1fadbfb60 100644 --- a/test/unit/responsive/components/dropdown-test.js +++ b/test/unit/responsive/components/dropdown-test.js @@ -1,6 +1,6 @@ -const React = require('react') const assert = require('assert') +const h = require('react-hyperscript') const sinon = require('sinon') const path = require('path') const Dropdown = require(path.join(__dirname, '..', '..', '..', '..', 'ui', 'app', 'components', 'app', 'dropdowns', 'index.js')).Dropdown @@ -39,19 +39,23 @@ describe('Dropdown components', function () { onClick = sinon.spy() store = createMockStore(mockState) - component = mountWithStore(( - - -
  • Item 1
  • -
  • Item 2
  • -
    + component = mountWithStore(h( + Dropdown, + dropdownComponentProps, + [ + h('style', ` + .drop-menu-item:hover { background:rgb(235, 235, 235); } + .drop-menu-item i { margin: 11px; } + `), + h('li', { + closeMenu, + onClick, + }, 'Item 1'), + h('li', { + closeMenu, + onClick, + }, 'Item 2'), + ] ), store) dropdownComponent = component }) diff --git a/test/unit/test-utils.js b/test/unit/test-utils.js index de508c504..7d0ae4d91 100644 --- a/test/unit/test-utils.js +++ b/test/unit/test-utils.js @@ -10,9 +10,7 @@ async function assertRejects (asyncFn, regExp) { try { await asyncFn() } catch (error) { - f = () => { - throw error - } + f = () => { throw error } } finally { assert.throws(f, regExp) } diff --git a/test/unit/ui/app/actions.spec.js b/test/unit/ui/app/actions.spec.js index fffc14ab8..9ed21e729 100644 --- a/test/unit/ui/app/actions.spec.js +++ b/test/unit/ui/app/actions.spec.js @@ -755,6 +755,7 @@ describe('Actions', () => { 'domain': { 'name': 'Ether Mainl', 'version': '1', + 'chainId': 1, 'verifyingContract': '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', }, 'message': { @@ -772,12 +773,11 @@ describe('Actions', () => { } beforeEach(() => { - metamaskController.newUnsignedTypedMessage(msgParamsV3, null, 'V3') + metamaskController.newUnsignedTypedMessage(msgParamsV3, 'V3') messages = metamaskController.typedMessageManager.getUnapprovedMsgs() typedMessages = metamaskController.typedMessageManager.messages msgId = Object.keys(messages)[0] typedMessages[0].msgParams.metamaskId = parseInt(msgId) - signTypedMsgSpy = sinon.stub(background, 'signTypedMessage') }) afterEach(() => { @@ -786,6 +786,7 @@ describe('Actions', () => { it('calls signTypedMsg in background with no error', () => { const store = mockStore() + signTypedMsgSpy = sinon.stub(background, 'signTypedMessage') store.dispatch(actions.signTypedMsg(msgParamsV3)) assert(signTypedMsgSpy.calledOnce) @@ -800,6 +801,8 @@ describe('Actions', () => { { type: 'DISPLAY_WARNING', value: 'error' }, ] + signTypedMsgSpy = sinon.stub(background, 'signTypedMessage') + signTypedMsgSpy.callsFake((_, callback) => { callback(new Error('error')) }) diff --git a/test/unit/util_test.js b/test/unit/util_test.js index ffb220276..87f57b218 100644 --- a/test/unit/util_test.js +++ b/test/unit/util_test.js @@ -1,15 +1,13 @@ -const assert = require('assert') -const sinon = require('sinon') +var assert = require('assert') +var sinon = require('sinon') const ethUtil = require('ethereumjs-util') -const path = require('path') -const util = require(path.join(__dirname, '..', '..', 'ui', 'app', 'helpers', 'utils', 'util.js')) +var path = require('path') +var util = require(path.join(__dirname, '..', '..', 'ui', 'app', 'helpers', 'utils', 'util.js')) describe('util', function () { - let ethInWei = '1' - for (let i = 0; i < 18; i++) { - ethInWei += '0' - } + var ethInWei = '1' + for (var i = 0; i < 18; i++) { ethInWei += '0' } beforeEach(function () { this.sinon = sinon.createSandbox() @@ -47,52 +45,52 @@ describe('util', function () { describe('#addressSummary', function () { it('should add case-sensitive checksum', function () { - const address = '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825' - const result = util.addressSummary(address) + var address = '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825' + var result = util.addressSummary(address) assert.equal(result, '0xFDEa65C8...b825') }) it('should accept arguments for firstseg, lastseg, and keepPrefix', function () { - const address = '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825' - const result = util.addressSummary(address, 4, 4, false) + var address = '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825' + var result = util.addressSummary(address, 4, 4, false) assert.equal(result, 'FDEa...b825') }) }) describe('#isValidAddress', function () { it('should allow 40-char non-prefixed hex', function () { - const address = 'fdea65c8e26263f6d9a1b5de9555d2931a33b825' - const result = util.isValidAddress(address) + var address = 'fdea65c8e26263f6d9a1b5de9555d2931a33b825' + var result = util.isValidAddress(address) assert.ok(result) }) it('should allow 42-char non-prefixed hex', function () { - const address = '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825' - const result = util.isValidAddress(address) + var address = '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825' + var result = util.isValidAddress(address) assert.ok(result) }) it('should not allow less non hex-prefixed', function () { - const address = 'fdea65c8e26263f6d9a1b5de9555d2931a33b85' - const result = util.isValidAddress(address) + var address = 'fdea65c8e26263f6d9a1b5de9555d2931a33b85' + var result = util.isValidAddress(address) assert.ok(!result) }) it('should not allow less hex-prefixed', function () { - const address = '0xfdea65ce26263f6d9a1b5de9555d2931a33b85' - const result = util.isValidAddress(address) + var address = '0xfdea65ce26263f6d9a1b5de9555d2931a33b85' + var result = util.isValidAddress(address) assert.ok(!result) }) it('should recognize correct capitalized checksum', function () { - const address = '0xFDEa65C8e26263F6d9A1B5de9555D2931A33b825' - const result = util.isValidAddress(address) + var address = '0xFDEa65C8e26263F6d9A1B5de9555D2931A33b825' + var result = util.isValidAddress(address) assert.ok(result) }) it('should recognize incorrect capitalized checksum', function () { - const address = '0xFDea65C8e26263F6d9A1B5de9555D2931A33b825' - const result = util.isValidAddress(address) + var address = '0xFDea65C8e26263F6d9A1B5de9555D2931A33b825' + var result = util.isValidAddress(address) assert.ok(!result) }) @@ -107,58 +105,58 @@ describe('util', function () { describe('#numericBalance', function () { it('should return a BN 0 if given nothing', function () { - const result = util.numericBalance() + var result = util.numericBalance() assert.equal(result.toString(10), 0) }) it('should work with hex prefix', function () { - const result = util.numericBalance('0x012') + var result = util.numericBalance('0x012') assert.equal(result.toString(10), '18') }) it('should work with no hex prefix', function () { - const result = util.numericBalance('012') + var result = util.numericBalance('012') assert.equal(result.toString(10), '18') }) }) describe('#formatBalance', function () { it('when given nothing', function () { - const result = util.formatBalance() + var result = util.formatBalance() assert.equal(result, 'None', 'should return "None"') }) it('should return eth as string followed by ETH', function () { - const input = new ethUtil.BN(ethInWei, 10).toJSON() - const result = util.formatBalance(input, 4) + var input = new ethUtil.BN(ethInWei, 10).toJSON() + var result = util.formatBalance(input, 4) assert.equal(result, '1.0000 ETH') }) it('should return eth as string followed by ETH', function () { - const input = new ethUtil.BN(ethInWei, 10).div(new ethUtil.BN('2', 10)).toJSON() - const result = util.formatBalance(input, 3) + var input = new ethUtil.BN(ethInWei, 10).div(new ethUtil.BN('2', 10)).toJSON() + var result = util.formatBalance(input, 3) assert.equal(result, '0.500 ETH') }) it('should display specified decimal points', function () { - const input = '0x128dfa6a90b28000' - const result = util.formatBalance(input, 2) + var input = '0x128dfa6a90b28000' + var result = util.formatBalance(input, 2) assert.equal(result, '1.33 ETH') }) it('should default to 3 decimal points', function () { - const input = '0x128dfa6a90b28000' - const result = util.formatBalance(input) + var input = '0x128dfa6a90b28000' + var result = util.formatBalance(input) assert.equal(result, '1.337 ETH') }) it('should show 2 significant digits for tiny balances', function () { - const input = '0x1230fa6a90b28' - const result = util.formatBalance(input) + var input = '0x1230fa6a90b28' + var result = util.formatBalance(input) assert.equal(result, '0.00032 ETH') }) it('should not parse the balance and return value with 2 decimal points with ETH at the end', function () { - const value = '1.2456789' - const needsParse = false - const result = util.formatBalance(value, 2, needsParse) + var value = '1.2456789' + var needsParse = false + var result = util.formatBalance(value, 2, needsParse) assert.equal(result, '1.24 ETH') }) }) @@ -166,7 +164,7 @@ describe('util', function () { describe('normalizing values', function () { describe('#normalizeToWei', function () { it('should convert an eth to the appropriate equivalent values', function () { - const valueTable = { + var valueTable = { wei: '1000000000000000000', kwei: '1000000000000000', mwei: '1000000000000', @@ -181,11 +179,11 @@ describe('util', function () { // gether:'0.000000001', // tether:'0.000000000001', } - const oneEthBn = new ethUtil.BN(ethInWei, 10) + var oneEthBn = new ethUtil.BN(ethInWei, 10) - for (const currency in valueTable) { - const value = new ethUtil.BN(valueTable[currency], 10) - const output = util.normalizeToWei(value, currency) + for (var currency in valueTable) { + var value = new ethUtil.BN(valueTable[currency], 10) + var output = util.normalizeToWei(value, currency) assert.equal(output.toString(10), valueTable.wei, `value of ${output.toString(10)} ${currency} should convert to ${oneEthBn}`) } }) @@ -193,66 +191,66 @@ describe('util', function () { describe('#normalizeEthStringToWei', function () { it('should convert decimal eth to pure wei BN', function () { - const input = '1.23456789' - const output = util.normalizeEthStringToWei(input) + var input = '1.23456789' + var output = util.normalizeEthStringToWei(input) assert.equal(output.toString(10), '1234567890000000000') }) it('should convert 1 to expected wei', function () { - const input = '1' - const output = util.normalizeEthStringToWei(input) + var input = '1' + var output = util.normalizeEthStringToWei(input) assert.equal(output.toString(10), ethInWei) }) it('should account for overflow numbers gracefully by dropping extra precision.', function () { - const input = '1.11111111111111111111' - const output = util.normalizeEthStringToWei(input) + var input = '1.11111111111111111111' + var output = util.normalizeEthStringToWei(input) assert.equal(output.toString(10), '1111111111111111111') }) it('should not truncate very exact wei values that do not have extra precision.', function () { - const input = '1.100000000000000001' - const output = util.normalizeEthStringToWei(input) + var input = '1.100000000000000001' + var output = util.normalizeEthStringToWei(input) assert.equal(output.toString(10), '1100000000000000001') }) }) describe('#normalizeNumberToWei', function () { it('should handle a simple use case', function () { - const input = 0.0002 - const output = util.normalizeNumberToWei(input, 'ether') - const str = output.toString(10) + var input = 0.0002 + var output = util.normalizeNumberToWei(input, 'ether') + var str = output.toString(10) assert.equal(str, '200000000000000') }) it('should convert a kwei number to the appropriate equivalent wei', function () { - const result = util.normalizeNumberToWei(1.111, 'kwei') + var result = util.normalizeNumberToWei(1.111, 'kwei') assert.equal(result.toString(10), '1111', 'accepts decimals') }) it('should convert a ether number to the appropriate equivalent wei', function () { - const result = util.normalizeNumberToWei(1.111, 'ether') + var result = util.normalizeNumberToWei(1.111, 'ether') assert.equal(result.toString(10), '1111000000000000000', 'accepts decimals') }) }) describe('#isHex', function () { it('should return true when given a hex string', function () { - const result = util.isHex('c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2') + var result = util.isHex('c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2') assert(result) }) it('should return false when given a non-hex string', function () { - const result = util.isHex('c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714imnotreal') + var result = util.isHex('c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714imnotreal') assert(!result) }) it('should return false when given a string containing a non letter/number character', function () { - const result = util.isHex('c3ab8ff13720!8ad9047dd39466b3c%8974e592c2fa383d4a396071imnotreal') + var result = util.isHex('c3ab8ff13720!8ad9047dd39466b3c%8974e592c2fa383d4a396071imnotreal') assert(!result) }) it('should return true when given a hex string with hex-prefix', function () { - const result = util.isHex('0xc3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2') + var result = util.isHex('0xc3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2') assert(result) }) }) diff --git a/test/web3/schema.js b/test/web3/schema.js index 9dc528a57..336060431 100644 --- a/test/web3/schema.js +++ b/test/web3/schema.js @@ -1,6 +1,6 @@ /* eslint no-unused-vars: 0 */ -const params = { +var params = { // diffrent params used in the methods param: [], blockHashParams: '0xb3b20624f8f0f86eb50dd04688409e5cea4bd02d700bf6e79e9384d47d6a5a35', @@ -93,7 +93,7 @@ const params = { }, } -const methods = { +var methods = { hexaNumberMethods: { // these are the methods which have output in the form of hexa decimal numbers eth_blockNumber: ['eth_blockNumber', params.param, 'Q'], diff --git a/test/web3/web3.js b/test/web3/web3.js index 3431ee8ba..8f319f38e 100644 --- a/test/web3/web3.js +++ b/test/web3/web3.js @@ -1,6 +1,6 @@ /* eslint no-undef: 0 */ -const json = methods +var json = methods web3.currentProvider.enable().then(() => { diff --git a/ui/app/components/app/account-details/account-details.component.js b/ui/app/components/app/account-details/account-details.component.js index e75e777a6..55078cee0 100644 --- a/ui/app/components/app/account-details/account-details.component.js +++ b/ui/app/components/app/account-details/account-details.component.js @@ -19,11 +19,9 @@ export default class AccountDetails extends Component { static propTypes = { hideSidebar: PropTypes.func, showAccountDetailModal: PropTypes.func, - showConnectedSites: PropTypes.func.isRequired, label: PropTypes.string.isRequired, checksummedAddress: PropTypes.string.isRequired, name: PropTypes.string.isRequired, - history: PropTypes.object.isRequired, } state = { @@ -50,7 +48,6 @@ export default class AccountDetails extends Component { const { hideSidebar, showAccountDetailModal, - showConnectedSites, label, checksummedAddress, name, @@ -68,19 +65,14 @@ export default class AccountDetails extends Component {
    {label}
    -
    - +
    + {name} -
    - - -
    +
    list.concat(keyring.accounts), []) @@ -76,8 +71,6 @@ export default class AccountMenu extends PureComponent { const keyring = keyrings.find(kr => { return kr.accounts.includes(simpleAddress) || kr.accounts.includes(identity.address) }) - const addressDomains = addressConnectedDomainMap[identity.address] || {} - const iconAndNameForOpenDomain = addressDomains[originOfCurrentTab] return (
    - { iconAndNameForOpenDomain - ? ( -
    - -
    - ) - : null - } { this.renderKeyringType(keyring) } { this.renderRemoveAccount(keyring, identity) } @@ -171,11 +156,9 @@ export default class AccountMenu extends PureComponent { case 'Simple Key Pair': label = t('imported') break - default: - return null } - return ( + return label && (
    { label }
    @@ -268,12 +251,12 @@ export default class AccountMenu extends PureComponent { }) history.push(NEW_ACCOUNT_ROUTE) }} - icon={( + icon={ - )} + } text={t('createAccount')} /> - )} + } text={t('importAccount')} /> - )} + } text={t('connectHardwareWallet')} /> @@ -343,12 +326,12 @@ export default class AccountMenu extends PureComponent { }, }) }} - icon={( + icon={ - )} + } text={t('settings')} /> diff --git a/ui/app/components/app/account-menu/account-menu.container.js b/ui/app/components/app/account-menu/account-menu.container.js index 00a0666ec..ae2e28e76 100644 --- a/ui/app/components/app/account-menu/account-menu.container.js +++ b/ui/app/components/app/account-menu/account-menu.container.js @@ -11,12 +11,7 @@ import { showInfoPage, showModal, } from '../../../store/actions' -import { - getAddressConnectedDomainMap, - getMetaMaskAccounts, - getOriginOfCurrentTab, -} from '../../../selectors/selectors' - +import { getMetaMaskAccounts } from '../../../selectors/selectors' import AccountMenu from './account-menu.component' function mapStateToProps (state) { @@ -28,8 +23,6 @@ function mapStateToProps (state) { keyrings, identities, accounts: getMetaMaskAccounts(state), - addressConnectedDomainMap: getAddressConnectedDomainMap(state), - originOfCurrentTab: getOriginOfCurrentTab(state), } } diff --git a/ui/app/components/app/account-menu/index.scss b/ui/app/components/app/account-menu/index.scss index 93b9766d3..614e19104 100644 --- a/ui/app/components/app/account-menu/index.scss +++ b/ui/app/components/app/account-menu/index.scss @@ -175,8 +175,4 @@ opacity: 1; } } - - &__icon-list { - display: flex; - } } diff --git a/ui/app/components/app/account-panel.js b/ui/app/components/app/account-panel.js index 3ffd97887..e61cb8ad6 100644 --- a/ui/app/components/app/account-panel.js +++ b/ui/app/components/app/account-panel.js @@ -1,5 +1,6 @@ -import React, { Component } from 'react' const inherits = require('util').inherits +const Component = require('react').Component +const h = require('react-hyperscript') import Identicon from '../ui/identicon' const formatBalance = require('../../helpers/utils/util').formatBalance const addressSummary = require('../../helpers/utils/util').addressSummary @@ -13,12 +14,12 @@ function AccountPanel () { } AccountPanel.prototype.render = function () { - const state = this.props - const identity = state.identity || {} - const account = state.account || {} - const isFauceting = state.isFauceting + var state = this.props + var identity = state.identity || {} + var account = state.account || {} + var isFauceting = state.isFauceting - const panelState = { + var panelState = { key: `accountPanel${identity.address}`, identiconKey: identity.address, identiconLabel: identity.name || '', @@ -32,24 +33,39 @@ AccountPanel.prototype.render = function () { } return ( -
    -
    - - {panelState.identiconLabel.substring(0, 7) + '...'} -
    -
    - {panelState.attributes.map((attr) => ( -
    - - {attr.value} -
    - ))} -
    -
    + + h('.identity-panel.flex-row.flex-space-between', { + style: { + flex: '1 0 auto', + cursor: panelState.onClick ? 'pointer' : undefined, + }, + onClick: panelState.onClick, + }, [ + + // account identicon + h('.identicon-wrapper.flex-column.select-none', [ + h(Identicon, { + address: panelState.identiconKey, + imageify: state.imageifyIdenticons, + }), + h('span.font-small', panelState.identiconLabel.substring(0, 7) + '...'), + ]), + + // account address, balance + h('.identity-data.flex-column.flex-justify-center.flex-grow.select-none', [ + + panelState.attributes.map((attr) => { + return h('.flex-row.flex-space-between', { + key: '' + Math.round(Math.random() * 1000000), + }, [ + h('label.font-small.no-select', attr.key), + h('span.font-small', attr.value), + ]) + }), + ]), + + ]) + ) } diff --git a/ui/app/components/app/app-header/app-header.component.js b/ui/app/components/app/app-header/app-header.component.js index bd01558c5..e1bc0cf24 100644 --- a/ui/app/components/app/app-header/app-header.component.js +++ b/ui/app/components/app/app-header/app-header.component.js @@ -89,8 +89,7 @@ export default class AppHeader extends PureComponent { return (
    + className={classnames('app-header', { 'app-header--back-drop': isUnlocked })}>
    { + this.updateValidity(event) + }, + onChange: (event) => { + this.updateValidity(event) + const value = (event.target.value === '') ? '' : event.target.value + + + const scaledNumber = this.upsize(value, scale, precision) + const precisionBN = new BN(scaledNumber, 10) + onChange(precisionBN, event.target.checkValidity()) + }, + onInvalid: (event) => { + const msg = this.constructWarning() + if (msg === state.invalid) { + return + } + this.setState({ invalid: msg }) + event.preventDefault() + return false + }, + }), + h('div', { + style: { + color: ' #AEAEAE', + fontSize: '12px', + marginLeft: '5px', + marginRight: '6px', + width: '20px', + }, + }, suffix), + ]), + + state.invalid ? h('span.error', { + style: { + position: 'absolute', + right: '0px', + textAlign: 'right', + transform: 'translateY(26px)', + padding: '3px', + background: 'rgba(255,255,255,0.85)', + zIndex: '1', + textTransform: 'capitalize', + border: '2px solid #E20202', + }, + }, state.invalid) : null, + ]) + ) +} + +BnAsDecimalInput.prototype.setValid = function () { + this.setState({ invalid: null }) +} + +BnAsDecimalInput.prototype.updateValidity = function (event) { + const target = event.target + const value = this.props.value + const newValue = target.value + + if (value === newValue) { + return + } + + const valid = target.checkValidity() + + if (valid) { + this.setState({ invalid: null }) + } +} + +BnAsDecimalInput.prototype.constructWarning = function () { + const { name, min, max, scale, suffix } = this.props + const newMin = min && this.downsize(min.toString(10), scale) + const newMax = max && this.downsize(max.toString(10), scale) + let message = name ? name + ' ' : '' + + if (min && max) { + message += this.context.t('betweenMinAndMax', [`${newMin} ${suffix}`, `${newMax} ${suffix}`]) + } else if (min) { + message += this.context.t('greaterThanMin', [`${newMin} ${suffix}`]) + } else if (max) { + message += this.context.t('lessThanMax', [`${newMax} ${suffix}`]) + } else { + message += this.context.t('invalidInput') + } + + return message +} + + +BnAsDecimalInput.prototype.downsize = function (number, scale) { + // if there is no scaling, simply return the number + if (scale === 0) { + return Number(number) + } else { + // if the scale is the same as the precision, account for this edge case. + var adjustedNumber = number + while (adjustedNumber.length < scale) { + adjustedNumber = '0' + adjustedNumber + } + return Number(adjustedNumber.slice(0, -scale) + '.' + adjustedNumber.slice(-scale)) + } +} + +BnAsDecimalInput.prototype.upsize = function (number, scale, precision) { + var stringArray = number.toString().split('.') + var decimalLength = stringArray[1] ? stringArray[1].length : 0 + var newString = stringArray[0] + + // If there is scaling and decimal parts exist, integrate them in. + if ((scale !== 0) && (decimalLength !== 0)) { + newString += stringArray[1].slice(0, precision) + } + + // Add 0s to account for the upscaling. + for (var i = decimalLength; i < scale; i++) { + newString += '0' + } + return newString +} diff --git a/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js b/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js index b973ad84d..0cc4d8262 100644 --- a/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js +++ b/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js @@ -47,11 +47,9 @@ const ConfirmPageContainerSummary = props => {
    { - hideSubtitle || ( -
    - { subtitleComponent || subtitle } -
    - ) + hideSubtitle ||
    + { subtitleComponent || subtitle } +
    } ) diff --git a/ui/app/components/app/confirm-page-container/confirm-page-container-header/confirm-page-container-header.component.js b/ui/app/components/app/confirm-page-container/confirm-page-container-header/confirm-page-container-header.component.js index 4b91b4ddb..898d59068 100644 --- a/ui/app/components/app/confirm-page-container/confirm-page-container-header/confirm-page-container-header.component.js +++ b/ui/app/components/app/confirm-page-container/confirm-page-container-header/confirm-page-container-header.component.js @@ -34,40 +34,36 @@ export default class ConfirmPageContainerHeader extends Component { return (
    { !showAccountInHeader - ? ( -
    + + onEdit()} > - - onEdit()} - > - { this.context.t('edit') } - -
    - ) + { this.context.t('edit') } + +
    : null } { showAccountInHeader - ? ( -
    -
    - -
    -
    - { addressSlicer(accountAddress) } -
    + ?
    +
    +
    - ) +
    + { addressSlicer(accountAddress) } +
    +
    : null } { !isFullScreen && } diff --git a/ui/app/components/app/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js b/ui/app/components/app/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js index dd1fd2da4..c24d24b17 100755 --- a/ui/app/components/app/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js +++ b/ui/app/components/app/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js @@ -5,28 +5,23 @@ const ConfirmPageContainerNavigation = props => { const { onNextTx, totalTx, positionOfCurrentTx, nextTxId, prevTxId, showNavigation, firstTx, lastTx, ofText, requestsWaitingText } = props return ( -
    -
    + }}>
    onNextTx(firstTx)} - > + onClick={() => onNextTx(firstTx)}>
    onNextTx(prevTxId)} - > + onClick={() => onNextTx(prevTxId)}>
    @@ -42,18 +37,15 @@ const ConfirmPageContainerNavigation = props => { className="confirm-page-container-navigation__container" style={{ visibility: nextTxId ? 'initial' : 'hidden', - }} - > + }}>
    onNextTx(nextTxId)} - > + onClick={() => onNextTx(nextTxId)}>
    onNextTx(lastTx)} - > + onClick={() => onNextTx(lastTx)}>
    diff --git a/ui/app/components/app/confirm-page-container/confirm-page-container.component.js b/ui/app/components/app/confirm-page-container/confirm-page-container.component.js index 9ebae3d95..86ce2bff7 100644 --- a/ui/app/components/app/confirm-page-container/confirm-page-container.component.js +++ b/ui/app/components/app/confirm-page-container/confirm-page-container.component.js @@ -133,17 +133,15 @@ export default class ConfirmPageContainer extends Component { > { hideSenderToRecipient ? null - : ( - - ) + : } { diff --git a/ui/app/components/app/connected-sites-list/connected-sites-list.component.js b/ui/app/components/app/connected-sites-list/connected-sites-list.component.js deleted file mode 100644 index a04f87486..000000000 --- a/ui/app/components/app/connected-sites-list/connected-sites-list.component.js +++ /dev/null @@ -1,157 +0,0 @@ -import React, { Component } from 'react' -import PropTypes from 'prop-types' -import classnames from 'classnames' -import Button from '../../ui/button' -import IconWithFallBack from '../../ui/icon-with-fallback' - -export default class ConnectedSitesList extends Component { - static contextTypes = { - t: PropTypes.func, - } - - static propTypes = { - renderableDomains: PropTypes.arrayOf(PropTypes.shape({ - name: PropTypes.string, - icon: PropTypes.string, - key: PropTypes.string, - lastConnectedTime: PropTypes.string, - permissionDescriptions: PropTypes.array, - })).isRequired, - domains: PropTypes.object, - showDisconnectAccountModal: PropTypes.func.isRequired, - showDisconnectAllModal: PropTypes.func.isRequired, - tabToConnect: PropTypes.object, - legacyExposeAccounts: PropTypes.func.isRequired, - selectedAddress: PropTypes.string.isRequired, - getOpenMetamaskTabsIds: PropTypes.func.isRequired, - } - - state = { - expandedDomain: '', - } - - componentWillMount () { - const { getOpenMetamaskTabsIds } = this.props - getOpenMetamaskTabsIds() - } - - handleDomainItemClick (domainKey) { - if (this.state.expandedDomain === domainKey) { - this.setState({ expandedDomain: '' }) - } else { - this.setState({ expandedDomain: domainKey }) - } - } - - render () { - const { - renderableDomains, - domains, - showDisconnectAccountModal, - showDisconnectAllModal, - tabToConnect, - legacyExposeAccounts, - selectedAddress, - } = this.props - const { expandedDomain } = this.state - const { t } = this.context - - return ( -
    - { - renderableDomains.map((domain, domainIndex) => { - const domainIsExpanded = expandedDomain === domain.key - return ( -
    -
    this.handleDomainItemClick(domain.key) }> -
    - - -
    -
    -
    - { domain.extensionId ? t('externalExtension') : domain.name } -
    -
    - { domain.lastConnectedTime - ? ( -
    - { t('domainLastConnect', [domain.lastConnectedTime]) } -
    - ) - : null - } - {domainIsExpanded - ? ( -
    - { domain.extensionId ? t('extensionId', [domain.extensionId]) : domain.secondaryName } -
    - ) - : null - } -
    -
    -
    - { domainIsExpanded ? : } -
    -
    - { domainIsExpanded - ? ( -
    -
    - { - domain.permissionDescriptions.map((description, pdIndex) => { - return ( -
    - -
    - { description } -
    -
    - ) - }) - } -
    -
    showDisconnectAccountModal(domain.key, domains[domain.key]) } - > - { t('disconnectAccount') } -
    -
    - ) - : null - } -
    - ) - }) - } -
    -
    - -
    - { tabToConnect - ? ( -
    - -
    - ) - : null - } -
    -
    - ) - } -} diff --git a/ui/app/components/app/connected-sites-list/connected-sites-list.container.js b/ui/app/components/app/connected-sites-list/connected-sites-list.container.js deleted file mode 100644 index de45831a9..000000000 --- a/ui/app/components/app/connected-sites-list/connected-sites-list.container.js +++ /dev/null @@ -1,57 +0,0 @@ -import { connect } from 'react-redux' -import { compose } from 'recompose' - -import ConnectedSitesList from './connected-sites-list.component' -import { - showModal, - legacyExposeAccounts, - getOpenMetamaskTabsIds, -} from '../../../store/actions' -import { - getRenderablePermissionsDomains, - getPermissionsDomains, - getAddressConnectedToCurrentTab, - getSelectedAddress, -} from '../../../selectors/selectors' -import { getOriginFromUrl } from '../../../helpers/utils/util' - -const mapStateToProps = state => { - const addressConnectedToCurrentTab = getAddressConnectedToCurrentTab(state) - const { openMetaMaskTabs } = state.appState - const { title, url, id } = state.activeTab - - let tabToConnect - - if (!addressConnectedToCurrentTab && url && !openMetaMaskTabs[id]) { - tabToConnect = { - title, - origin: getOriginFromUrl(url), - } - } - - return { - domains: getPermissionsDomains(state), - renderableDomains: getRenderablePermissionsDomains(state), - tabToConnect, - selectedAddress: getSelectedAddress(state), - } -} - -const mapDispatchToProps = dispatch => { - return { - showDisconnectAccountModal: (domainKey, domain) => { - dispatch(showModal({ name: 'DISCONNECT_ACCOUNT', domainKey, domain })) - }, - showDisconnectAllModal: () => { - dispatch(showModal({ name: 'DISCONNECT_ALL' })) - }, - legacyExposeAccounts: (origin, account) => { - dispatch(legacyExposeAccounts(origin, [account])) - }, - getOpenMetamaskTabsIds: () => dispatch(getOpenMetamaskTabsIds()), - } -} - -export default compose( - connect(mapStateToProps, mapDispatchToProps) -)(ConnectedSitesList) diff --git a/ui/app/components/app/connected-sites-list/index.js b/ui/app/components/app/connected-sites-list/index.js deleted file mode 100644 index 431a3a8eb..000000000 --- a/ui/app/components/app/connected-sites-list/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './connected-sites-list.container' diff --git a/ui/app/components/app/connected-sites-list/index.scss b/ui/app/components/app/connected-sites-list/index.scss deleted file mode 100644 index c2d98d2aa..000000000 --- a/ui/app/components/app/connected-sites-list/index.scss +++ /dev/null @@ -1,125 +0,0 @@ -.connected-sites-list { - font-family: Roboto; - font-style: normal; - font-weight: normal; - - &__domain, &__domain--expanded { - border-bottom: 1px solid #c4c4c4; - } - - &__domain { - cursor: pointer; - } - - &__domain--expanded { - background: #FAFBFC; - cursor: initial; - } - - &__domain-item { - display: flex; - justify-content: space-between; - align-items: center; - padding: 16px; - } - - &__domain-item-info-container { - display: flex; - } - - &__identicon-container { - position: relative; - display: flex; - justify-content: center; - align-items: center; - height: 32px; - width: 32px; - } - - &__identicon-border { - height: 32px; - width: 32px; - border-radius: 50%; - border: 1px solid #F2F3F4; - position: absolute; - background: #FFFFFF; - } - - &__identicon { - width: 24px; - height: 24px; - z-index: 1; - - &--default { - z-index: 1; - color: black; - } - } - - &__domain-info { - display: flex; - flex-direction: column; - margin-left: 16px; - } - - &__domain-names { - display: flex; - align-items: center; - } - - &__domain-name { - font-size: 18px; - color: #24292E; - } - - &__domain-origin, &__domain-last-connected { - font-size: 12px; - color: #6A737D; - } - - &__domain-last-connected { - margin-top: 2px; - } - - &__expand-arrow { - align-self: flex-start; - margin-top: 6px; - } - - &__permissions { - padding-left: 16px; - padding-bottom: 24px; - } - - &__permission { - display: flex; - align-items: center; - color: #6A737D; - margin-left: 16px; - } - - &__permission-description { - margin-left: 18px; - } - - &__disconnect { - margin-top: 24px; - color: #D73A49; - cursor: pointer; - } - - &__bottom-buttons { - display: flex; - align-items: center; - } - - &__disconnect-all { - padding: 10px; - width: 50%; - } - - &__connect-to { - padding: 10px; - width: 50%; - } -} \ No newline at end of file diff --git a/ui/app/components/app/contact-list/recipient-group/recipient-group.component.js b/ui/app/components/app/contact-list/recipient-group/recipient-group.component.js index 20e2b3494..a2248326e 100644 --- a/ui/app/components/app/contact-list/recipient-group/recipient-group.component.js +++ b/ui/app/components/app/contact-list/recipient-group/recipient-group.component.js @@ -15,11 +15,9 @@ export default function RecipientGroup ({ label, items, onSelect, selectedAddres return (
    - {label && ( -
    - {label} -
    - )} + {label &&
    + {label} +
    } { items.map(({ address, name }) => (
    { + event.preventDefault() + event.stopPropagation() + copyToClipboard(value) + this.debounceRestore() + }, + }, children)) +} + +Copyable.prototype.debounceRestore = function () { + this.setState({ copied: true }) + clearTimeout(this.timeout) + this.timeout = setTimeout(() => { + this.setState({ copied: false }) + }, 850) +} diff --git a/ui/app/components/app/customize-gas-modal/gas-modal-card.js b/ui/app/components/app/customize-gas-modal/gas-modal-card.js index 5ba96dd82..23754d819 100644 --- a/ui/app/components/app/customize-gas-modal/gas-modal-card.js +++ b/ui/app/components/app/customize-gas-modal/gas-modal-card.js @@ -1,6 +1,8 @@ -import React, {Component} from 'react' +const Component = require('react').Component +const h = require('react-hyperscript') const inherits = require('util').inherits const InputNumber = require('../input-number.js') +// const GasSlider = require('./gas-slider.js') module.exports = GasModalCard @@ -9,30 +11,44 @@ function GasModalCard () { Component.call(this) } -GasModalCard.prototype.render = function GasModalCard () { +GasModalCard.prototype.render = function () { const { + // memo, onChange, unitLabel, value, min, + // max, step, title, copy, } = this.props - return ( -
    -
    {title}
    -
    {copy}
    - -
    - ) + return h('div.send-v2__gas-modal-card', [ + + h('div.send-v2__gas-modal-card__title', {}, title), + + h('div.send-v2__gas-modal-card__copy', {}, copy), + + h(InputNumber, { + unitLabel, + step, + // max, + min, + placeholder: '0', + value, + onChange, + }), + + // h(GasSlider, { + // value, + // step, + // max, + // min, + // onChange, + // }), + + ]) + } diff --git a/ui/app/components/app/customize-gas-modal/gas-slider.js b/ui/app/components/app/customize-gas-modal/gas-slider.js new file mode 100644 index 000000000..69fd6f985 --- /dev/null +++ b/ui/app/components/app/customize-gas-modal/gas-slider.js @@ -0,0 +1,50 @@ +// const Component = require('react').Component +// const h = require('react-hyperscript') +// const inherits = require('util').inherits + +// module.exports = GasSlider + +// inherits(GasSlider, Component) +// function GasSlider () { +// Component.call(this) +// } + +// GasSlider.prototype.render = function () { +// const { +// memo, +// identities, +// onChange, +// unitLabel, +// value, +// id, +// step, +// max, +// min, +// } = this.props + +// return h('div.gas-slider', [ + +// h('input.gas-slider__input', { +// type: 'range', +// step, +// max, +// min, +// value, +// id: 'gasSlider', +// onChange: event => onChange(event.target.value), +// }, []), + +// h('div.gas-slider__bar', [ + +// h('div.gas-slider__low'), + +// h('div.gas-slider__mid'), + +// h('div.gas-slider__high'), + +// ]), + +// ]) + +// } + diff --git a/ui/app/components/app/customize-gas-modal/index.js b/ui/app/components/app/customize-gas-modal/index.js index 7bbe725b2..1f9436810 100644 --- a/ui/app/components/app/customize-gas-modal/index.js +++ b/ui/app/components/app/customize-gas-modal/index.js @@ -1,5 +1,6 @@ -import React, { Component } from 'react' +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 BigNumber = require('bignumber.js') @@ -327,72 +328,69 @@ CustomizeGasModal.prototype.render = function () { toNumericBase: 'dec', }) - if (gasIsLoading) { - return null - } + return !gasIsLoading && h('div.send-v2__customize-gas', {}, [ + h('div.send-v2__customize-gas__content', { + }, [ + h('div.send-v2__customize-gas__header', {}, [ - const { t } = this.context + h('div.send-v2__customize-gas__title', this.context.t('customGas')), - return ( -
    -
    -
    -
    - {this.context.t('customGas')} -
    -
    -
    -
    - this.convertAndSetGasPrice(value)} - title={t('gasPrice')} - copy={t('gasPriceCalculation')} - gasIsLoading={gasIsLoading} - /> - this.convertAndSetGasLimit(value)} - title={t('gasLimit')} - copy={t('gasLimitCalculation')} - gasIsLoading={gasIsLoading} - /> -
    -
    - {error && ( -
    - {error} -
    - )} -
    this.revert()}> - {t('revert')} -
    -
    - - -
    -
    -
    -
    - ) + h('div.send-v2__customize-gas__close', { + onClick: hideModal, + }), + + ]), + + h('div.send-v2__customize-gas__body', {}, [ + + h(GasModalCard, { + value: convertedGasPrice, + min: forceGasMin || MIN_GAS_PRICE_GWEI, + step: 1, + onChange: value => this.convertAndSetGasPrice(value), + title: this.context.t('gasPrice'), + copy: this.context.t('gasPriceCalculation'), + gasIsLoading, + }), + + h(GasModalCard, { + value: convertedGasLimit, + min: 1, + step: 1, + onChange: value => this.convertAndSetGasLimit(value), + title: this.context.t('gasLimit'), + copy: this.context.t('gasLimitCalculation'), + gasIsLoading, + }), + + ]), + + h('div.send-v2__customize-gas__footer', {}, [ + + error && h('div.send-v2__customize-gas__error-message', [ + error, + ]), + + h('div.send-v2__customize-gas__revert', { + onClick: () => this.revert(), + }, [this.context.t('revert')]), + + h('div.send-v2__customize-gas__buttons', [ + h(Button, { + type: 'default', + className: 'send-v2__customize-gas__cancel', + onClick: this.props.hideModal, + }, [this.context.t('cancel')]), + h(Button, { + type: 'secondary', + className: 'send-v2__customize-gas__save', + onClick: () => !error && this.save(newGasPrice, gasLimit, gasTotal), + disabled: error, + }, [this.context.t('save')]), + ]), + + ]), + + ]), + ]) } diff --git a/ui/app/components/app/dropdowns/account-details-dropdown.js b/ui/app/components/app/dropdowns/account-details-dropdown.js index dc09b8c64..cf2aa8ae8 100644 --- a/ui/app/components/app/dropdowns/account-details-dropdown.js +++ b/ui/app/components/app/dropdowns/account-details-dropdown.js @@ -1,12 +1,10 @@ -import React, { Component } from 'react' +const Component = require('react').Component const PropTypes = require('prop-types') -const { compose } = require('recompose') -const { withRouter } = require('react-router-dom') +const h = require('react-hyperscript') const inherits = require('util').inherits const connect = require('react-redux').connect const actions = require('../../../store/actions') const { getSelectedIdentity, getRpcPrefsForCurrentProvider } = require('../../../selectors/selectors') -const { CONNECTED_ROUTE } = require('../../../helpers/constants/routes') const genAccountLink = require('../../../../lib/account-link.js') const { Menu, Item, CloseArea } = require('./components/menu') @@ -15,7 +13,7 @@ AccountDetailsDropdown.contextTypes = { metricsEvent: PropTypes.func, } -module.exports = compose(withRouter, connect(mapStateToProps, mapDispatchToProps))(AccountDetailsDropdown) +module.exports = connect(mapStateToProps, mapDispatchToProps)(AccountDetailsDropdown) function mapStateToProps (state) { return { @@ -52,7 +50,7 @@ AccountDetailsDropdown.prototype.onClose = function (e) { this.props.onClose() } -AccountDetailsDropdown.prototype.render = function AccountDetailsDropdown () { +AccountDetailsDropdown.prototype.render = function () { const { selectedIdentity, network, @@ -61,7 +59,6 @@ AccountDetailsDropdown.prototype.render = function AccountDetailsDropdown () { viewOnEtherscan, showRemoveAccountConfirmationModal, rpcPrefs, - history, } = this.props const address = selectedIdentity.address @@ -72,104 +69,71 @@ AccountDetailsDropdown.prototype.render = function AccountDetailsDropdown () { const isRemovable = keyring.type !== 'HD Key Tree' - return ( - - - { - e.stopPropagation() - this.context.metricsEvent({ - eventOpts: { - category: 'Navigation', - action: 'Account Options', - name: 'Clicked Expand View', - }, - }) - global.platform.openExtensionInBrowser() - this.props.onClose() - }} - text={this.context.t('expandView')} - icon={( - - )} - /> - { - e.stopPropagation() - showAccountDetailModal() - this.context.metricsEvent({ - eventOpts: { - category: 'Navigation', - action: 'Account Options', - name: 'Viewed Account Details', - }, - }) - this.props.onClose() - }} - text={this.context.t('accountDetails')} - icon={( - - )} - /> - { - e.stopPropagation() - this.context.metricsEvent({ - eventOpts: { - category: 'Navigation', - action: 'Account Options', - name: 'Clicked View on Etherscan', - }, - }) - viewOnEtherscan(address, network, rpcPrefs) - this.props.onClose() - }} - text={ - rpcPrefs.blockExplorerUrl - ? this.context.t('viewinExplorer') - : this.context.t('viewOnEtherscan') - } - subText={ - rpcPrefs.blockExplorerUrl - ? rpcPrefs.blockExplorerUrl.match(/^https?:\/\/(.+)/)[1] - : null - } - icon={( - - )} - /> - { - e.stopPropagation() - this.context.metricsEvent({ - eventOpts: { - category: 'Navigation', - action: 'Account Options', - name: 'Opened Connected Sites', - }, - }) - history.push(CONNECTED_ROUTE) - }} - text={this.context.t('connectedSites')} - icon={( - - )} - /> - { - isRemovable - ? ( - { - e.stopPropagation() - showRemoveAccountConfirmationModal(selectedIdentity) - this.props.onClose() - }} - text={this.context.t('removeAccount')} - icon={} - /> - ) - : null - } - - ) + return h(Menu, { className: 'account-details-dropdown', isShowing: true }, [ + h(CloseArea, { + onClick: this.onClose, + }), + h(Item, { + onClick: (e) => { + e.stopPropagation() + this.context.metricsEvent({ + eventOpts: { + category: 'Navigation', + action: 'Account Options', + name: 'Clicked Expand View', + }, + }) + global.platform.openExtensionInBrowser() + this.props.onClose() + }, + text: this.context.t('expandView'), + icon: h(`img`, { src: 'images/expand.svg', style: { height: '15px' } }), + }), + h(Item, { + onClick: (e) => { + e.stopPropagation() + showAccountDetailModal() + this.context.metricsEvent({ + eventOpts: { + category: 'Navigation', + action: 'Account Options', + name: 'Viewed Account Details', + }, + }) + this.props.onClose() + }, + text: this.context.t('accountDetails'), + icon: h(`img`, { src: 'images/info.svg', style: { height: '15px' } }), + }), + h(Item, { + onClick: (e) => { + e.stopPropagation() + this.context.metricsEvent({ + eventOpts: { + category: 'Navigation', + action: 'Account Options', + name: 'Clicked View on Etherscan', + }, + }) + viewOnEtherscan(address, network, rpcPrefs) + this.props.onClose() + }, + text: (rpcPrefs.blockExplorerUrl + ? this.context.t('viewinExplorer') + : this.context.t('viewOnEtherscan')), + subText: (rpcPrefs.blockExplorerUrl + ? rpcPrefs.blockExplorerUrl.match(/^https?:\/\/(.+)/)[1] + : null), + icon: h(`img`, { src: 'images/open-etherscan.svg', style: { height: '15px' } }), + }), + isRemovable ? h(Item, { + onClick: (e) => { + e.stopPropagation() + showRemoveAccountConfirmationModal(selectedIdentity) + this.props.onClose() + }, + text: this.context.t('removeAccount'), + icon: h(`img`, { src: 'images/hide.svg', style: { height: '15px' } }), + }) : null, + ]) } diff --git a/ui/app/components/app/dropdowns/components/dropdown.js b/ui/app/components/app/dropdowns/components/dropdown.js index ad6c1c514..cc966ffa5 100644 --- a/ui/app/components/app/dropdowns/components/dropdown.js +++ b/ui/app/components/app/dropdowns/components/dropdown.js @@ -1,5 +1,6 @@ -import React, { Component } from 'react' +const Component = require('react').Component const PropTypes = require('prop-types') +const h = require('react-hyperscript') const MenuDroppo = require('../../menu-droppo') const extend = require('xtend') @@ -22,28 +23,31 @@ class Dropdown extends Component { boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px', }, innerStyle) - return ( - - - { children } - + return h( + MenuDroppo, + { + containerClassName, + useCssTransition, + isOpen, + zIndex: 55, + onClickOutside, + style, + innerStyle: innerStyleDefaults, + }, + [ + h( + 'style', + ` + li.dropdown-menu-item:hover { + color:rgb(225, 225, 225); + background-color: rgba(255, 255, 255, 0.05); + border-radius: 4px; + } + li.dropdown-menu-item { color: rgb(185, 185, 185); } + ` + ), + ...children, + ] ) } } @@ -54,6 +58,7 @@ Dropdown.defaultProps = { Dropdown.propTypes = { isOpen: PropTypes.bool.isRequired, + onClick: PropTypes.func.isRequired, children: PropTypes.node, style: PropTypes.object.isRequired, onClickOutside: PropTypes.func, @@ -66,14 +71,14 @@ class DropdownMenuItem extends Component { render () { const { onClick, closeMenu, children, style } = this.props - return ( -
  • { + return h( + 'li.dropdown-menu-item', + { + onClick: () => { onClick() closeMenu() - }} - style={Object.assign({ + }, + style: Object.assign({ listStyle: 'none', padding: '8px 0px', fontSize: '18px', @@ -83,10 +88,9 @@ class DropdownMenuItem extends Component { justifyContent: 'flex-start', alignItems: 'center', color: 'white', - }, style)} - > - {children} -
  • + }, style), + }, + children ) } } diff --git a/ui/app/components/app/dropdowns/components/menu.js b/ui/app/components/app/dropdowns/components/menu.js index 669a50c63..63501eaa9 100644 --- a/ui/app/components/app/dropdowns/components/menu.js +++ b/ui/app/components/app/dropdowns/components/menu.js @@ -1,85 +1,53 @@ -import PropTypes from 'prop-types' -import React from 'react' -import classnames from 'classnames' +const inherits = require('util').inherits +const Component = require('react').Component +const h = require('react-hyperscript') -/** - * Menu component - * @return {Component|null} - */ -function Menu (props) { - const { className, children, isShowing } = props +inherits(Menu, Component) +function Menu () { Component.call(this) } + +Menu.prototype.render = function () { + const { className = '', children, isShowing } = this.props return isShowing - ?
    {children}
    - : null + ? h('div', { className: `menu ${className}` }, children) + : h('noscript') } -Menu.defaultProps = { - className: '', - isShowing: false, - children: null, -} +inherits(Item, Component) +function Item () { Component.call(this) } -Menu.propTypes = { - className: PropTypes.string, - children: PropTypes.node, - isShowing: PropTypes.bool, -} - -function Item (props) { +Item.prototype.render = function () { const { icon, children, text, subText, - className, + className = '', onClick, - } = props + } = this.props + const itemClassName = `menu__item ${className} ${onClick ? 'menu__item--clickable' : ''}` + const iconComponent = icon ? h('div.menu__item__icon', [icon]) : null + const textComponent = text ? h('div.menu__item__text', text) : null + const subTextComponent = subText ? h('div.menu__item__subtext', subText) : null - const itemClassName = classnames('menu__item', className, { - 'menu__item--clickable': Boolean(onClick), - }) return children - ?
    {children}
    - : ( -
    - {icon ?
    {icon}
    : null} - {text ?
    {text}
    : null} - {subText ?
    {subText}
    : null} -
    + ? h('div', { className: itemClassName, onClick }, children) + : h('div.menu__item', { className: itemClassName, onClick }, [ iconComponent, textComponent, subTextComponent ] + .filter(d => Boolean(d)) ) } -Item.defaultProps = { - children: null, - icon: null, - text: null, - subText: null, - className: '', - onClick: null, +inherits(Divider, Component) +function Divider () { Component.call(this) } + +Divider.prototype.render = function () { + return h('div.menu__divider') } -Item.propTypes = { - icon: PropTypes.node, - children: PropTypes.node, - text: PropTypes.node, - subText: PropTypes.node, - className: PropTypes.string, - onClick: PropTypes.func, -} +inherits(CloseArea, Component) +function CloseArea () { Component.call(this) } -function Divider () { - return
    -} - -function CloseArea ({ onClick }) { - return
    -} - -CloseArea.propTypes = { - onClick: PropTypes.func.isRequired, +CloseArea.prototype.render = function () { + return h('div.menu__close-area', { onClick: this.props.onClick }) } module.exports = { Menu, Item, Divider, CloseArea } diff --git a/ui/app/components/app/dropdowns/components/network-dropdown-icon.js b/ui/app/components/app/dropdowns/components/network-dropdown-icon.js index ca318c9e5..d4a2c2ff7 100644 --- a/ui/app/components/app/dropdowns/components/network-dropdown-icon.js +++ b/ui/app/components/app/dropdowns/components/network-dropdown-icon.js @@ -1,56 +1,47 @@ -import PropTypes from 'prop-types' -import React from 'react' +const inherits = require('util').inherits +const Component = require('react').Component +const h = require('react-hyperscript') -function NetworkDropdownIcon (props) { + +inherits(NetworkDropdownIcon, Component) +module.exports = NetworkDropdownIcon + +function NetworkDropdownIcon () { + Component.call(this) +} + +NetworkDropdownIcon.prototype.render = function () { const { backgroundColor, isSelected, - innerBorder, - diameter, + innerBorder = 'none', + diameter = '12', loading, - } = props + } = this.props return loading - ? ( - - - - ) - : ( -
    -
    -
    + ? h('span.pointer.network-indicator', { + style: { + display: 'flex', + alignItems: 'center', + flexDirection: 'row', + }, + }, [ + h('img', { + style: { + width: '27px', + }, + src: 'images/loading.svg', + }), + ]) + : h(`.menu-icon-circle${isSelected ? '--active' : ''}`, {}, + h('div', { + style: { + background: backgroundColor, + border: innerBorder, + height: `${diameter}px`, + width: `${diameter}px`, + }, + }) ) } - -NetworkDropdownIcon.defaultProps = { - backgroundColor: undefined, - loading: false, - innerBorder: 'none', - diameter: '12', - isSelected: false, -} - -NetworkDropdownIcon.propTypes = { - backgroundColor: PropTypes.string, - loading: PropTypes.bool, - innerBorder: PropTypes.string, - diameter: PropTypes.string, - isSelected: PropTypes.bool, -} - -module.exports = NetworkDropdownIcon diff --git a/ui/app/components/app/dropdowns/network-dropdown.js b/ui/app/components/app/dropdowns/network-dropdown.js index cf56919c4..e6a24ef11 100644 --- a/ui/app/components/app/dropdowns/network-dropdown.js +++ b/ui/app/components/app/dropdowns/network-dropdown.js @@ -1,5 +1,6 @@ -import PropTypes from 'prop-types' -import React, {Component} from 'react' +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 { withRouter } = require('react-router-dom') @@ -66,9 +67,10 @@ module.exports = compose( // TODO: specify default props and proptypes -NetworkDropdown.prototype.render = function NetworkDropdown () { - const { provider: { type: providerType, rpcTarget: activeNetwork }, setNetworksTabAddMode } = this.props - const rpcListDetail = this.props.frequentRpcListDetail +NetworkDropdown.prototype.render = function () { + const props = this.props + const { provider: { type: providerType, rpcTarget: activeNetwork }, setNetworksTabAddMode } = props + const rpcListDetail = props.frequentRpcListDetail const isOpen = this.props.networkDropdownOpen const dropdownMenuItemStyle = { fontSize: '16px', @@ -76,206 +78,201 @@ NetworkDropdown.prototype.render = function NetworkDropdown () { padding: '12px 0', } - return ( - { - const { classList } = event.target - const isInClassList = className => classList.contains(className) - const notToggleElementIndex = R.findIndex(isInClassList)(notToggleElementClassnames) + return h(Dropdown, { + isOpen, + onClickOutside: (event) => { + const { classList } = event.target + const isInClassList = className => classList.contains(className) + const notToggleElementIndex = R.findIndex(isInClassList)(notToggleElementClassnames) - if (notToggleElementIndex === -1) { - this.props.hideNetworkDropdown() - } - }} - containerClassName="network-droppo" - zIndex={55} - style={{ - position: 'absolute', - top: '58px', - width: '309px', - zIndex: '55px', - }} - innerStyle={{ - padding: '18px 8px', - }} - > -
    -
    - {this.context.t('networks')} -
    -
    -
    - {this.context.t('defaultNetwork')} -
    -
    - this.props.hideNetworkDropdown()} - onClick={() => this.handleClick('mainnet')} - style={{ ...dropdownMenuItemStyle, borderColor: '#038789' }} - > - { - providerType === 'mainnet' - ? - :
    - } - - - {this.context.t('mainnet')} - -
    - this.props.hideNetworkDropdown()} - onClick={() => this.handleClick('ropsten')} - style={dropdownMenuItemStyle} - > - { - providerType === 'ropsten' - ? - :
    - } - - - {this.context.t('ropsten')} - -
    - this.props.hideNetworkDropdown()} - onClick={() => this.handleClick('kovan')} - style={dropdownMenuItemStyle} - > - { - providerType === 'kovan' - ? - :
    - } - - - {this.context.t('kovan')} - -
    - this.props.hideNetworkDropdown()} - onClick={() => this.handleClick('rinkeby')} - style={dropdownMenuItemStyle} - > - { - providerType === 'rinkeby' - ? - :
    - } - - - {this.context.t('rinkeby')} - -
    - this.props.hideNetworkDropdown()} - onClick={() => this.handleClick('goerli')} - style={dropdownMenuItemStyle} - > - { - providerType === 'goerli' - ? - :
    - } - - - {this.context.t('goerli')} - -
    - this.props.hideNetworkDropdown()} - onClick={() => this.handleClick('localhost')} - style={dropdownMenuItemStyle} - > - { - providerType === 'localhost' - ? - :
    - } - - - {this.context.t('localhost')} - -
    - {this.renderCustomOption(this.props.provider)} - {this.renderCommonRpc(rpcListDetail, this.props.provider)} - this.props.hideNetworkDropdown()} - onClick={() => { + if (notToggleElementIndex === -1) { + this.props.hideNetworkDropdown() + } + }, + containerClassName: 'network-droppo', + zIndex: 55, + style: { + position: 'absolute', + top: '58px', + width: '309px', + zIndex: '55px', + }, + innerStyle: { + padding: '18px 8px', + }, + }, [ + + h('div.network-dropdown-header', {}, [ + h('div.network-dropdown-title', {}, this.context.t('networks')), + + h('div.network-dropdown-divider'), + + h('div.network-dropdown-content', + {}, + this.context.t('defaultNetwork') + ), + ]), + + h( + DropdownMenuItem, + { + key: 'main', + closeMenu: () => this.props.hideNetworkDropdown(), + onClick: () => this.handleClick('mainnet'), + style: { ...dropdownMenuItemStyle, borderColor: '#038789' }, + }, + [ + providerType === 'mainnet' ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'), + h(NetworkDropdownIcon, { + backgroundColor: '#29B6AF', // $java + isSelected: providerType === 'mainnet', + }), + h('span.network-name-item', { + style: { + color: providerType === 'mainnet' ? '#ffffff' : '#9b9b9b', + }, + }, this.context.t('mainnet')), + ] + ), + + h( + DropdownMenuItem, + { + key: 'ropsten', + closeMenu: () => this.props.hideNetworkDropdown(), + onClick: () => this.handleClick('ropsten'), + style: dropdownMenuItemStyle, + }, + [ + providerType === 'ropsten' ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'), + h(NetworkDropdownIcon, { + backgroundColor: '#ff4a8d', // $wild-strawberry + isSelected: providerType === 'ropsten', + }), + h('span.network-name-item', { + style: { + color: providerType === 'ropsten' ? '#ffffff' : '#9b9b9b', + }, + }, this.context.t('ropsten')), + ] + ), + + h( + DropdownMenuItem, + { + key: 'kovan', + closeMenu: () => this.props.hideNetworkDropdown(), + onClick: () => this.handleClick('kovan'), + style: dropdownMenuItemStyle, + }, + [ + providerType === 'kovan' ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'), + h(NetworkDropdownIcon, { + backgroundColor: '#7057ff', // $cornflower-blue + isSelected: providerType === 'kovan', + }), + h('span.network-name-item', { + style: { + color: providerType === 'kovan' ? '#ffffff' : '#9b9b9b', + }, + }, this.context.t('kovan')), + ] + ), + + h( + DropdownMenuItem, + { + key: 'rinkeby', + closeMenu: () => this.props.hideNetworkDropdown(), + onClick: () => this.handleClick('rinkeby'), + style: dropdownMenuItemStyle, + }, + [ + providerType === 'rinkeby' ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'), + h(NetworkDropdownIcon, { + backgroundColor: '#f6c343', // $saffron + isSelected: providerType === 'rinkeby', + }), + h('span.network-name-item', { + style: { + color: providerType === 'rinkeby' ? '#ffffff' : '#9b9b9b', + }, + }, this.context.t('rinkeby')), + ] + ), + + 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'), + style: dropdownMenuItemStyle, + }, + [ + providerType === 'localhost' ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'), + h(NetworkDropdownIcon, { + isSelected: providerType === 'localhost', + innerBorder: '1px solid #9b9b9b', + }), + h('span.network-name-item', { + style: { + color: providerType === 'localhost' ? '#ffffff' : '#9b9b9b', + }, + }, this.context.t('localhost')), + ] + ), + + this.renderCustomOption(props.provider), + this.renderCommonRpc(rpcListDetail, props.provider), + + h( + DropdownMenuItem, + { + closeMenu: () => this.props.hideNetworkDropdown(), + onClick: () => { setNetworksTabAddMode(true) this.props.history.push(NETWORKS_ROUTE) - }} - style={dropdownMenuItemStyle} - > - { - activeNetwork === 'custom' - ? - :
    - } - - - {this.context.t('customRPC')} - -
    - - ) + }, + style: dropdownMenuItemStyle, + }, + [ + activeNetwork === 'custom' ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'), + h(NetworkDropdownIcon, { + isSelected: activeNetwork === 'custom', + innerBorder: '1px solid #9b9b9b', + }), + h('span.network-name-item', { + style: { + color: activeNetwork === 'custom' ? '#ffffff' : '#9b9b9b', + }, + }, this.context.t('customRPC')), + ] + ), + + ]) } NetworkDropdown.prototype.handleClick = function (newProviderType) { @@ -335,56 +332,45 @@ NetworkDropdown.prototype.renderCommonRpc = function (rpcListDetail, provider) { return null } else { const chainId = entry.chainId - return ( - this.props.hideNetworkDropdown()} - onClick={() => props.setRpcTarget(rpc, chainId, ticker, nickname)} - style={{ + return h( + DropdownMenuItem, + { + key: `common${rpc}`, + closeMenu: () => this.props.hideNetworkDropdown(), + onClick: () => props.setRpcTarget(rpc, chainId, ticker, nickname), + style: { fontSize: '16px', lineHeight: '20px', padding: '12px 0', - }} - > - { - currentRpcTarget - ? - :
    - } - - - {nickname || rpc} - - { - e.stopPropagation() - props.delRpcTarget(rpc) - }} - /> -
    + }, + }, + [ + currentRpcTarget ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'), + h('i.fa.fa-question-circle.fa-med.menu-icon-circle'), + h('span.network-name-item', { + style: { + color: currentRpcTarget ? '#ffffff' : '#9b9b9b', + }, + }, nickname || rpc), + h('i.fa.fa-times.delete', + { + onClick: (e) => { + e.stopPropagation() + props.delRpcTarget(rpc) + }, + }), + ] ) } }) } -/** - * @return {Component|null} - */ -NetworkDropdown.prototype.renderCustomOption = function NetworkDropdown (provider) { +NetworkDropdown.prototype.renderCustomOption = function (provider) { const { rpcTarget, type, ticker, nickname } = provider - const network = this.props.network + const props = this.props + const network = props.network - if (type !== 'rpc') { - return null - } + if (type !== 'rpc') return null switch (rpcTarget) { @@ -392,28 +378,27 @@ NetworkDropdown.prototype.renderCustomOption = function NetworkDropdown (provide return null default: - return ( - this.props.setRpcTarget(rpcTarget, network, ticker, nickname)} - closeMenu={() => this.props.hideNetworkDropdown()} - style={{ + return h( + DropdownMenuItem, + { + key: rpcTarget, + onClick: () => props.setRpcTarget(rpcTarget, network, ticker, nickname), + closeMenu: () => this.props.hideNetworkDropdown(), + style: { fontSize: '16px', lineHeight: '20px', padding: '12px 0', - }} - > - - - - {nickname || rpcTarget} - - + }, + }, nickname || rpcTarget), + ] ) } } diff --git a/ui/app/components/app/dropdowns/simple-dropdown.js b/ui/app/components/app/dropdowns/simple-dropdown.js index 081674397..bba088ed1 100644 --- a/ui/app/components/app/dropdowns/simple-dropdown.js +++ b/ui/app/components/app/dropdowns/simple-dropdown.js @@ -1,18 +1,15 @@ -import classnames from 'classnames' -import PropTypes from 'prop-types' -import React, { Component } from 'react' -import R from 'ramda' +const { Component } = require('react') +const PropTypes = require('prop-types') +const h = require('react-hyperscript') +const classnames = require('classnames') +const R = require('ramda') class SimpleDropdown extends Component { - static propTypes = { - options: PropTypes.array.isRequired, - placeholder: PropTypes.string, - onSelect: PropTypes.func, - selectedOption: PropTypes.string, - } - - state = { - isOpen: false, + constructor (props) { + super(props) + this.state = { + isOpen: false, + } } getDisplayValue () { @@ -29,58 +26,67 @@ class SimpleDropdown extends Component { } toggleOpen () { - this.setState((prevState) => ({ - isOpen: !prevState.isOpen, - })) + const { isOpen } = this.state + this.setState({ isOpen: !isOpen }) } renderOptions () { const { options, onSelect, selectedOption } = this.props - return ( -
    -
    { - event.stopPropagation() - this.handleClose() - }} - /> -
    - {options.map((option) => ( -
    { + event.stopPropagation() + this.handleClose() + }, + }), + h('div.simple-dropdown__options', [ + ...options.map(option => { + return h( + 'div.simple-dropdown__option', + { + className: classnames({ 'simple-dropdown__option--selected': option.value === selectedOption, - })} - key={option.value} - onClick={() => { + }), + key: option.value, + onClick: () => { if (option.value !== selectedOption) { onSelect(option.value) } this.handleClose() - }} - > - {option.displayValue || option.value} -
    - ))} -
    -
    - ) + }, + }, + option.displayValue || option.value, + ) + }), + ]), + ]) } render () { const { placeholder } = this.props const { isOpen } = this.state - return ( -
    this.toggleOpen()}> -
    {this.getDisplayValue() || placeholder || 'Select'}
    - - {isOpen && this.renderOptions()} -
    + return h( + 'div.simple-dropdown', + { + onClick: () => this.toggleOpen(), + }, + [ + h('div.simple-dropdown__selected', this.getDisplayValue() || placeholder || 'Select'), + h('i.fa.fa-caret-down.fa-lg.simple-dropdown__caret'), + isOpen && this.renderOptions(), + ] ) } } +SimpleDropdown.propTypes = { + options: PropTypes.array.isRequired, + placeholder: PropTypes.string, + onSelect: PropTypes.func, + selectedOption: PropTypes.string, +} + module.exports = SimpleDropdown diff --git a/ui/app/components/app/dropdowns/tests/menu.test.js b/ui/app/components/app/dropdowns/tests/menu.test.js index b3bc34fa9..6413c0c2c 100644 --- a/ui/app/components/app/dropdowns/tests/menu.test.js +++ b/ui/app/components/app/dropdowns/tests/menu.test.js @@ -11,7 +11,7 @@ describe('Dropdown Menu Components', () => { beforeEach(() => { wrapper = shallow( - + ) }) @@ -31,14 +31,14 @@ describe('Dropdown Menu Components', () => { ) }) it('add className based on props', () => { - assert.equal(wrapper.find('.menu__item').prop('className'), 'menu__item test foo1 menu__item--clickable') + assert.equal(wrapper.find('.menu__item').prop('className'), 'menu__item menu__item test className menu__item--clickable') }) it('simulates onClick called', () => { @@ -73,11 +73,9 @@ describe('Dropdown Menu Components', () => { const onClickSpy = sinon.spy() beforeEach(() => { - wrapper = shallow(( - - )) + wrapper = shallow() }) it('simulates click', () => { diff --git a/ui/app/components/app/dropdowns/tests/network-dropdown-icon.test.js b/ui/app/components/app/dropdowns/tests/network-dropdown-icon.test.js index ce3853a63..ed7b7e253 100644 --- a/ui/app/components/app/dropdowns/tests/network-dropdown-icon.test.js +++ b/ui/app/components/app/dropdowns/tests/network-dropdown-icon.test.js @@ -7,14 +7,12 @@ describe('Network Dropdown Icon', () => { let wrapper beforeEach(() => { - wrapper = shallow(( - - )) + wrapper = shallow() }) it('adds style props based on props', () => { 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 98cba1d2e..4a81b973f 100644 --- a/ui/app/components/app/dropdowns/tests/network-dropdown.test.js +++ b/ui/app/components/app/dropdowns/tests/network-dropdown.test.js @@ -57,7 +57,7 @@ describe('Network Dropdown', () => { beforeEach(() => { wrapper = mountWithRouter( - , + , ) }) diff --git a/ui/app/components/app/dropdowns/token-menu-dropdown.js b/ui/app/components/app/dropdowns/token-menu-dropdown.js index 65c559f70..e2730aea2 100644 --- a/ui/app/components/app/dropdowns/token-menu-dropdown.js +++ b/ui/app/components/app/dropdowns/token-menu-dropdown.js @@ -1,5 +1,6 @@ -import PropTypes from 'prop-types' -import React, { Component } from 'react' +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') @@ -39,29 +40,29 @@ TokenMenuDropdown.prototype.onClose = function (e) { this.props.onClose() } -TokenMenuDropdown.prototype.render = function TokenMenuDropdown () { +TokenMenuDropdown.prototype.render = function () { const { showHideTokenConfirmationModal } = this.props - return ( - - - { - e.stopPropagation() - showHideTokenConfirmationModal(this.props.token) - this.props.onClose() - }} - text={this.context.t('hideToken')} - /> - { - e.stopPropagation() - const url = genAccountLink(this.props.token.address, this.props.network) - global.platform.openWindow({ url }) - this.props.onClose() - }} - text={this.context.t('viewOnEtherscan')} - /> - - ) + return h(Menu, { className: 'token-menu-dropdown', isShowing: true }, [ + h(CloseArea, { + onClick: this.onClose, + }), + h(Item, { + onClick: (e) => { + e.stopPropagation() + showHideTokenConfirmationModal(this.props.token) + this.props.onClose() + }, + text: this.context.t('hideToken'), + }), + h(Item, { + onClick: (e) => { + e.stopPropagation() + const url = genAccountLink(this.props.token.address, this.props.network) + global.platform.openWindow({ url }) + this.props.onClose() + }, + text: this.context.t('viewOnEtherscan'), + }), + ]) } 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 b881db6e5..7fb5aa6f4 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 @@ -119,12 +119,10 @@ export default class AdvancedGasInputs extends Component { value={value} onChange={onChange} /> -
    +
    onChange({ target: { value: value + 1 } })} @@ -161,21 +159,21 @@ export default class AdvancedGasInputs extends Component { errorText: gasPriceErrorText, errorType: gasPriceErrorType, } = this.gasPriceError({ insufficientBalance, customPriceIsSafe, isSpeedUp, gasPrice }) - const gasPriceErrorComponent = gasPriceErrorType ? ( + const gasPriceErrorComponent = gasPriceErrorType ?
    { gasPriceErrorText } -
    - ) : null +
    : + null const { errorText: gasLimitErrorText, errorType: gasLimitErrorType, } = this.gasLimitError({ insufficientBalance, gasLimit }) - const gasLimitErrorComponent = gasLimitErrorType ? ( + const gasLimitErrorComponent = gasLimitErrorType ?
    { gasLimitErrorText } -
    - ) : null +
    : + null return (
    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 19e3aa776..306dd03a0 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 @@ -83,19 +83,17 @@ export default class AdvancedTabContent extends Component { />
    { isEthereumNetwork - ? ( -
    -
    { t('liveGasPricePredictions') }
    - {!gasEstimatesLoading - ? - : - } -
    - { t('slower') } - { t('faster') } -
    + ?
    +
    { t('liveGasPricePredictions') }
    + {!gasEstimatesLoading + ? + : + } +
    + { t('slower') } + { t('faster') }
    - ) +
    :
    { t('chartOnlyAvailableEth') }
    }
    diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/tests/advanced-tab-content-component.test.js b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/tests/advanced-tab-content-component.test.js index 16b15df41..05075f3ba 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/tests/advanced-tab-content-component.test.js +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/tests/advanced-tab-content-component.test.js @@ -18,20 +18,18 @@ describe('AdvancedTabContent Component', function () { let wrapper beforeEach(() => { - wrapper = shallow(( - - )) + wrapper = shallow() }) afterEach(() => { diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/basic-tab-content.component.js b/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/basic-tab-content.component.js index 94d22ee4a..c804abe3a 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/basic-tab-content.component.js +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/basic-tab-content.component.js @@ -21,13 +21,11 @@ export default class BasicTabContent extends Component {
    { t('estimatedProcessingTimes') }
    { t('selectAHigherGasFee') }
    {!gasPriceButtonGroupProps.loading - ? ( - - ) + ? : }
    { t('acceleratingATransaction') }
    diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/tests/basic-tab-content-component.test.js b/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/tests/basic-tab-content-component.test.js index 80fbffe05..0989ac677 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/tests/basic-tab-content-component.test.js +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/tests/basic-tab-content-component.test.js @@ -38,11 +38,9 @@ describe('BasicTabContent Component', function () { let wrapper beforeEach(() => { - wrapper = shallow(( - - )) + wrapper = shallow() }) describe('render', () => { 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 daf91eae3..f405cb7b9 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 @@ -160,14 +160,13 @@ export default class GasModalPageContainer extends Component { return ( - {tabsToRender.map(({ name, content }, i) => ( - -
    - { content } - { this.renderInfoRows(newTotalFiat, newTotalEth, sendAmount, transactionFee) } -
    -
    - ))} + {tabsToRender.map(({ name, content }, i) => +
    + { content } + { this.renderInfoRows(newTotalFiat, newTotalEth, sendAmount, transactionFee) } +
    +
    + )}
    ) } diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-component.test.js b/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-component.test.js index 50efb46f0..3e416db49 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-component.test.js +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-component.test.js @@ -63,25 +63,23 @@ describe('GasModalPageContainer Component', function () { let wrapper beforeEach(() => { - wrapper = shallow(( - 'mockupdateCustomGasPrice'} - updateCustomGasLimit={() => 'mockupdateCustomGasLimit'} - customGasPrice={21} - customGasLimit={54321} - gasPriceButtonGroupProps={mockGasPriceButtonGroupProps} - infoRowProps={mockInfoRowProps} - currentTimeEstimate="1 min 31 sec" - customGasPriceInHex="mockCustomGasPriceInHex" - customGasLimitInHex="mockCustomGasLimitInHex" - insufficientBalance={false} - disableSave={false} - /> - )) + wrapper = shallow( 'mockupdateCustomGasPrice'} + updateCustomGasLimit={() => 'mockupdateCustomGasLimit'} + customGasPrice={21} + customGasLimit={54321} + gasPriceButtonGroupProps={mockGasPriceButtonGroupProps} + infoRowProps={mockInfoRowProps} + currentTimeEstimate="1 min 31 sec" + customGasPriceInHex="mockCustomGasPriceInHex" + customGasLimitInHex="mockCustomGasLimitInHex" + insufficientBalance={false} + disableSave={false} + />) }) afterEach(() => { @@ -136,12 +134,10 @@ describe('GasModalPageContainer Component', function () { it('should pass the correct renderTabs property to PageContainer', () => { sinon.stub(GP, 'renderTabs').returns('mockTabs') - const renderTabsWrapperTester = shallow(( - - ), { context: { t: (str1, str2) => str2 ? str1 + str2 : str1 } }) + const renderTabsWrapperTester = shallow(, { context: { t: (str1, str2) => str2 ? str1 + str2 : str1 } }) const { tabsComponent } = renderTabsWrapperTester.find(PageContainer).props() assert.equal(tabsComponent, 'mockTabs') GasModalPageContainer.prototype.renderTabs.restore() @@ -188,26 +184,24 @@ describe('GasModalPageContainer Component', function () { }) it('should not render the basic tab if hideBasic is true', () => { - wrapper = shallow(( - 'mockupdateCustomGasPrice'} - updateCustomGasLimit={() => 'mockupdateCustomGasLimit'} - customGasPrice={21} - customGasLimit={54321} - gasPriceButtonGroupProps={mockGasPriceButtonGroupProps} - infoRowProps={mockInfoRowProps} - currentTimeEstimate="1 min 31 sec" - customGasPriceInHex="mockCustomGasPriceInHex" - customGasLimitInHex="mockCustomGasLimitInHex" - insufficientBalance={false} - disableSave={false} - hideBasic - /> - )) + wrapper = shallow( 'mockupdateCustomGasPrice'} + updateCustomGasLimit={() => 'mockupdateCustomGasLimit'} + customGasPrice={21} + customGasLimit={54321} + gasPriceButtonGroupProps={mockGasPriceButtonGroupProps} + infoRowProps={mockInfoRowProps} + currentTimeEstimate="1 min 31 sec" + customGasPriceInHex="mockCustomGasPriceInHex" + customGasLimitInHex="mockCustomGasLimitInHex" + insufficientBalance={false} + disableSave={false} + hideBasic + />) const renderTabsResult = wrapper.instance().renderTabs() const renderedTabs = shallow(renderTabsResult) 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 b578ff1ed..0412b3381 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,15 +49,13 @@ export default class GasPriceButtonGroup extends Component { className, showCheck, }) { - return ( -
    - { gasEstimateType &&
    { this.gasEstimateTypeLabel(gasEstimateType) }
    } - { timeEstimate &&
    { timeEstimate }
    } - { feeInPrimaryCurrency &&
    { feeInPrimaryCurrency }
    } - { feeInSecondaryCurrency &&
    { feeInSecondaryCurrency }
    } - { showCheck &&
    } -
    - ) + return (
    + { gasEstimateType &&
    { this.gasEstimateTypeLabel(gasEstimateType) }
    } + { timeEstimate &&
    { timeEstimate }
    } + { feeInPrimaryCurrency &&
    { feeInPrimaryCurrency }
    } + { feeInSecondaryCurrency &&
    { feeInSecondaryCurrency }
    } + { showCheck &&
    } +
    ) } renderButton ({ @@ -90,16 +88,14 @@ export default class GasPriceButtonGroup extends Component { return ( !buttonDataLoading - ? ( - - { gasButtonInfo.map((obj, index) => this.renderButton(obj, buttonPropsAndFlags, index)) } - - ) + ? + { gasButtonInfo.map((obj, index) => this.renderButton(obj, buttonPropsAndFlags, index)) } + :
    { this.context.t('loading') }
    ) } diff --git a/ui/app/components/app/gas-customization/gas-price-button-group/tests/gas-price-button-group-component.test.js b/ui/app/components/app/gas-customization/gas-price-button-group/tests/gas-price-button-group-component.test.js index 1d924f850..85f53d08e 100644 --- a/ui/app/components/app/gas-customization/gas-price-button-group/tests/gas-price-button-group-component.test.js +++ b/ui/app/components/app/gas-customization/gas-price-button-group/tests/gas-price-button-group-component.test.js @@ -48,11 +48,9 @@ describe('GasPriceButtonGroup Component', function () { let wrapper beforeEach(() => { - wrapper = shallow(( - - )) + wrapper = shallow() }) afterEach(() => { 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 452544abe..419cae0cd 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 @@ -214,9 +214,7 @@ export function generateChart (gasPrices, estimatedTimes, gasPricesMax, estimate tick: { values: [Math.floor(gasPrices[0]), Math.ceil(gasPricesMax)], outer: false, - format: function (val) { - return val + ' GWEI' - }, + format: function (val) { return val + ' GWEI' }, }, padding: {left: gasPricesMax / 50, right: gasPricesMax / 50}, label: { diff --git a/ui/app/components/app/gas-customization/gas-slider/gas-slider.component.js b/ui/app/components/app/gas-customization/gas-slider/gas-slider.component.js index 629d81f0e..8233dedb5 100644 --- a/ui/app/components/app/gas-customization/gas-slider/gas-slider.component.js +++ b/ui/app/components/app/gas-customization/gas-slider/gas-slider.component.js @@ -36,7 +36,7 @@ export default class GasSlider extends Component { onChange={event => onChange(event.target.value)} />
    -
    +
    {lowLabel} diff --git a/ui/app/components/app/index.scss b/ui/app/components/app/index.scss index 7578aa204..1afbebd00 100644 --- a/ui/app/components/app/index.scss +++ b/ui/app/components/app/index.scss @@ -38,7 +38,7 @@ @import '../../pages/index'; -@import 'permission-page-container/index'; +@import 'provider-page-container/index'; @import 'selected-account/index'; @@ -64,12 +64,18 @@ @import 'transaction-status/index'; +@import 'app-header/index'; + @import 'sidebars/index'; @import '../ui/unit-input/index'; @import 'gas-customization/gas-modal-page-container/index'; +@import 'gas-customization/gas-modal-page-container/index'; + +@import 'gas-customization/gas-modal-page-container/index'; + @import 'gas-customization/index'; @import 'gas-customization/gas-price-button-group/index'; @@ -81,7 +87,3 @@ @import 'multiple-notifications/index'; @import 'signature-request/index'; - -@import 'connected-sites-list/index'; - -@import '../ui/icon-with-fallback/index'; diff --git a/ui/app/components/app/input-number.js b/ui/app/components/app/input-number.js index 7506808b3..8a6ec725c 100644 --- a/ui/app/components/app/input-number.js +++ b/ui/app/components/app/input-number.js @@ -1,5 +1,5 @@ -import React from 'react' const Component = require('react').Component +const h = require('react-hyperscript') const inherits = require('util').inherits const { addCurrencies, @@ -28,9 +28,7 @@ function removeLeadingZeroes (str) { InputNumber.prototype.setValue = function (newValue) { newValue = removeLeadingZeroes(newValue) - if (newValue && !isValidInput(newValue)) { - return - } + if (newValue && !isValidInput(newValue)) return const { fixed, min = -1, max = Infinity, onChange } = this.props newValue = fixed ? newValue.toFixed(4) : newValue @@ -52,36 +50,32 @@ InputNumber.prototype.setValue = function (newValue) { } } -InputNumber.prototype.render = function InputNumber () { +InputNumber.prototype.render = function () { const { unitLabel, step = 1, placeholder, value } = this.props - return ( -
    - { - this.setValue(e.target.value) - }} - min={0} - /> - {unitLabel} -
    -
    this.setValue(addCurrencies(value, step, { toNumericBase: 'dec' }))} - > - -
    -
    this.setValue(subtractCurrencies(value, step, { toNumericBase: 'dec' }))} - > - -
    -
    -
    - ) + return h('div.customize-gas-input-wrapper', {}, [ + h('input', { + className: 'customize-gas-input', + value, + placeholder, + type: 'number', + onChange: e => { + this.setValue(e.target.value) + }, + min: 0, + }), + h('span.gas-tooltip-input-detail', {}, [unitLabel]), + h('div.gas-tooltip-input-arrows', {}, [ + h('div.gas-tooltip-input-arrow-wrapper', { + onClick: () => this.setValue(addCurrencies(value, step, { toNumericBase: 'dec' })), + }, [ + h('i.fa.fa-angle-up'), + ]), + h('div.gas-tooltip-input-arrow-wrapper', { + onClick: () => this.setValue(subtractCurrencies(value, step, { toNumericBase: 'dec' })), + }, [ + h('i.fa.fa-angle-down'), + ]), + ]), + ]) } 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 22419aabe..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 @@ -61,46 +61,42 @@ export default class LoadingNetworkScreen extends PureComponent { } renderLoadingScreenContent = () => { - return ( -
    - - {this.renderMessage()} -
    - ) + return
    + + {this.renderMessage()} +
    } renderErrorScreenContent = () => { const { showNetworkDropdown, setProviderArgs, setProviderType } = this.props - return ( -
    - 😞 - { this.context.t('somethingWentWrong') } -
    - + return
    + 😞 + { this.context.t('somethingWentWrong') } +
    + - -
    +
    - ) +
    } cancelCall = () => { diff --git a/ui/app/components/app/menu-droppo.js b/ui/app/components/app/menu-droppo.js index 57a0df2fe..a88cad4b4 100644 --- a/ui/app/components/app/menu-droppo.js +++ b/ui/app/components/app/menu-droppo.js @@ -1,4 +1,5 @@ -import React, { Component } from 'react' +const Component = require('react').Component +const h = require('react-hyperscript') const inherits = require('util').inherits const findDOMNode = require('react-dom').findDOMNode const ReactCSSTransitionGroup = require('react-transition-group/CSSTransitionGroup') @@ -26,44 +27,41 @@ MenuDroppoComponent.prototype.render = function () { style.zIndex = zIndex return ( -
    - - { - useCssTransition - ? ( - - {this.renderPrimary()} - - ) - : this.renderPrimary() - } -
    + .menu-droppo-leave.menu-droppo-leave-active { + transition: transform ${speed} ease-in-out; + transform: translateY(-200%); + } + `), + + useCssTransition + ? h(ReactCSSTransitionGroup, { + className: 'css-transition-group', + transitionName: 'menu-droppo', + transitionEnterTimeout: parseInt(speed), + transitionLeaveTimeout: parseInt(speed), + }, this.renderPrimary()) + : this.renderPrimary(), + ]) ) } @@ -76,9 +74,11 @@ MenuDroppoComponent.prototype.renderPrimary = function () { const innerStyle = this.props.innerStyle || {} return ( -
    - {this.props.children} -
    + h('.menu-droppo', { + key: 'menu-droppo-drawer', + style: innerStyle, + }, + [ this.props.children ]) ) } @@ -98,7 +98,7 @@ MenuDroppoComponent.prototype.componentDidMount = function () { this.globalClickHandler = this.globalClickOccurred.bind(this) document.body.addEventListener('click', this.globalClickHandler) // eslint-disable-next-line react/no-find-dom-node - const container = findDOMNode(this) + var container = findDOMNode(this) this.container = container } } @@ -122,7 +122,7 @@ MenuDroppoComponent.prototype.globalClickOccurred = function (event) { } function isDescendant (parent, child) { - let node = child.parentNode + var node = child.parentNode while (node !== null) { if (node === parent) { return true diff --git a/ui/app/components/app/modal/modal.component.js b/ui/app/components/app/modal/modal.component.js index 6c45160fd..f0fdd3bd5 100644 --- a/ui/app/components/app/modal/modal.component.js +++ b/ui/app/components/app/modal/modal.component.js @@ -16,7 +16,6 @@ export default class Modal extends PureComponent { submitType: PropTypes.string, submitText: PropTypes.string, submitDisabled: PropTypes.bool, - hideFooter: PropTypes.bool, // Cancel button (left button) onCancel: PropTypes.func, cancelType: PropTypes.string, @@ -42,7 +41,6 @@ export default class Modal extends PureComponent { cancelText, contentClass, containerClass, - hideFooter, } = this.props return ( @@ -63,32 +61,27 @@ export default class Modal extends PureComponent {
    { children }
    - { !hideFooter - ? ( -
    - { - onCancel && ( - - ) - } +
    + { + onCancel && ( -
    - ) - : null - } + ) + } + +
    ) } diff --git a/ui/app/components/app/modals/account-details-modal/account-details-modal.component.js b/ui/app/components/app/modals/account-details-modal/account-details-modal.component.js index b2ed1a8cc..1b9a6a718 100644 --- a/ui/app/components/app/modals/account-details-modal/account-details-modal.component.js +++ b/ui/app/components/app/modals/account-details-modal/account-details-modal.component.js @@ -56,7 +56,7 @@ export default class AccountDetailsModal extends Component { }} /> -
    +
    - ) + ? : null } diff --git a/ui/app/components/app/modals/account-modal-container.js b/ui/app/components/app/modals/account-modal-container.js new file mode 100644 index 000000000..b7ae0b5b8 --- /dev/null +++ b/ui/app/components/app/modals/account-modal-container.js @@ -0,0 +1,80 @@ +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') +const { getSelectedIdentity } = require('../../../selectors/selectors') +import Identicon from '../../ui/identicon' + +function mapStateToProps (state, ownProps) { + return { + selectedIdentity: ownProps.selectedIdentity || getSelectedIdentity(state), + } +} + +function mapDispatchToProps (dispatch) { + return { + hideModal: () => { + dispatch(actions.hideModal()) + }, + } +} + +inherits(AccountModalContainer, Component) +function AccountModalContainer () { + Component.call(this) +} + +AccountModalContainer.contextTypes = { + t: PropTypes.func, +} + +module.exports = connect(mapStateToProps, mapDispatchToProps)(AccountModalContainer) + + +AccountModalContainer.prototype.render = function () { + const { + selectedIdentity, + showBackButton = false, + backButtonAction, + } = this.props + let { children } = this.props + + if (children.constructor !== Array) { + children = [children] + } + + return h('div', { style: { borderRadius: '4px' }}, [ + h('div.account-modal-container', [ + + h('div', [ + + // Needs a border; requires changes to svg + h(Identicon, { + address: selectedIdentity.address, + diameter: 64, + style: {}, + }), + + ]), + + showBackButton && h('div.account-modal-back', { + onClick: backButtonAction, + }, [ + + h('i.fa.fa-angle-left.fa-lg'), + + h('span.account-modal-back__text', ' ' + this.context.t('back')), + + ]), + + h('div.account-modal-close', { + onClick: this.props.hideModal, + }), + + ...children, + + ]), + ]) +} diff --git a/ui/app/components/app/modals/account-modal-container/account-modal-container.component.js b/ui/app/components/app/modals/account-modal-container/account-modal-container.component.js deleted file mode 100644 index ed28f5fd1..000000000 --- a/ui/app/components/app/modals/account-modal-container/account-modal-container.component.js +++ /dev/null @@ -1,52 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import Identicon from '../../../ui/identicon' - -export default function AccountModalContainer (props, context) { - const { - selectedIdentity, - showBackButton, - backButtonAction, - hideModal, - children, - } = props - - return ( -
    -
    -
    - -
    - {showBackButton && ( -
    - - {' ' + context.t('back')} -
    - )} -
    - {children} -
    -
    - ) -} - -AccountModalContainer.contextTypes = { - t: PropTypes.func, -} - -AccountModalContainer.defaultProps = { - showBackButton: false, - children: null, - backButtonAction: undefined, -} - -AccountModalContainer.propTypes = { - selectedIdentity: PropTypes.object.isRequired, - showBackButton: PropTypes.bool, - backButtonAction: PropTypes.func, - hideModal: PropTypes.func.isRequired, - children: PropTypes.node, -} diff --git a/ui/app/components/app/modals/account-modal-container/account-modal-container.container.js b/ui/app/components/app/modals/account-modal-container/account-modal-container.container.js deleted file mode 100644 index ad118f92d..000000000 --- a/ui/app/components/app/modals/account-modal-container/account-modal-container.container.js +++ /dev/null @@ -1,20 +0,0 @@ -import { connect } from 'react-redux' -import { hideModal } from '../../../../store/actions' -import { getSelectedIdentity } from '../../../../selectors/selectors' -import AccountModalContainer from './account-modal-container.component' - -function mapStateToProps (state, ownProps) { - return { - selectedIdentity: ownProps.selectedIdentity || getSelectedIdentity(state), - } -} - -function mapDispatchToProps (dispatch) { - return { - hideModal: () => { - dispatch(hideModal()) - }, - } -} - -export default connect(mapStateToProps, mapDispatchToProps)(AccountModalContainer) diff --git a/ui/app/components/app/modals/account-modal-container/index.js b/ui/app/components/app/modals/account-modal-container/index.js deleted file mode 100644 index e37684b96..000000000 --- a/ui/app/components/app/modals/account-modal-container/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './account-modal-container.container' diff --git a/ui/app/components/app/modals/clear-approved-origins/clear-approved-origins.component.js b/ui/app/components/app/modals/clear-approved-origins/clear-approved-origins.component.js new file mode 100644 index 000000000..ceaa20a95 --- /dev/null +++ b/ui/app/components/app/modals/clear-approved-origins/clear-approved-origins.component.js @@ -0,0 +1,39 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import Modal, { ModalContent } from '../../modal' + +export default class ClearApprovedOrigins extends PureComponent { + static propTypes = { + hideModal: PropTypes.func.isRequired, + clearApprovedOrigins: PropTypes.func.isRequired, + } + + static contextTypes = { + t: PropTypes.func, + } + + handleClear = () => { + const { clearApprovedOrigins, hideModal } = this.props + clearApprovedOrigins() + hideModal() + } + + render () { + const { t } = this.context + + return ( + this.props.hideModal()} + submitText={t('ok')} + cancelText={t('nevermind')} + submitType="secondary" + > + + + ) + } +} diff --git a/ui/app/components/app/modals/disconnect-all/disconnect-all.container.js b/ui/app/components/app/modals/clear-approved-origins/clear-approved-origins.container.js similarity index 53% rename from ui/app/components/app/modals/disconnect-all/disconnect-all.container.js rename to ui/app/components/app/modals/clear-approved-origins/clear-approved-origins.container.js index 2415c3fa9..2276bc7e7 100644 --- a/ui/app/components/app/modals/disconnect-all/disconnect-all.container.js +++ b/ui/app/components/app/modals/clear-approved-origins/clear-approved-origins.container.js @@ -1,20 +1,16 @@ import { connect } from 'react-redux' import { compose } from 'recompose' -import { withRouter } from 'react-router-dom' import withModalProps from '../../../../helpers/higher-order-components/with-modal-props' -import DisconnectAll from './disconnect-all.component' -import { clearPermissions } from '../../../../store/actions' +import ClearApprovedOriginsComponent from './clear-approved-origins.component' +import { clearApprovedOrigins } from '../../../../store/actions' const mapDispatchToProps = dispatch => { return { - disconnectAll: () => { - dispatch(clearPermissions()) - }, + clearApprovedOrigins: () => dispatch(clearApprovedOrigins()), } } export default compose( withModalProps, - withRouter, connect(null, mapDispatchToProps) -)(DisconnectAll) +)(ClearApprovedOriginsComponent) diff --git a/ui/app/components/app/modals/clear-approved-origins/index.js b/ui/app/components/app/modals/clear-approved-origins/index.js new file mode 100644 index 000000000..b3e321995 --- /dev/null +++ b/ui/app/components/app/modals/clear-approved-origins/index.js @@ -0,0 +1 @@ +export { default } from './clear-approved-origins.container' diff --git a/ui/app/components/app/modals/confirm-remove-account/confirm-remove-account.component.js b/ui/app/components/app/modals/confirm-remove-account/confirm-remove-account.component.js index 7bea983b4..7fe79be5b 100644 --- a/ui/app/components/app/modals/confirm-remove-account/confirm-remove-account.component.js +++ b/ui/app/components/app/modals/confirm-remove-account/confirm-remove-account.component.js @@ -78,9 +78,7 @@ export default class ConfirmRemoveAccount extends Component { + target="_blank" href="https://metamask.zendesk.com/hc/en-us/articles/360015289932"> { t('learnMore') }
    diff --git a/ui/app/components/app/modals/deposit-ether-modal.js b/ui/app/components/app/modals/deposit-ether-modal.js new file mode 100644 index 000000000..f71e0619e --- /dev/null +++ b/ui/app/components/app/modals/deposit-ether-modal.js @@ -0,0 +1,213 @@ +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') +const { getNetworkDisplayName } = require('../../../../../app/scripts/controllers/network/util') + +import Button from '../../ui/button' + +let DIRECT_DEPOSIT_ROW_TITLE +let DIRECT_DEPOSIT_ROW_TEXT +let WYRE_ROW_TITLE +let WYRE_ROW_TEXT +let FAUCET_ROW_TITLE +let COINSWITCH_ROW_TITLE +let COINSWITCH_ROW_TEXT + +function mapStateToProps (state) { + return { + network: state.metamask.network, + address: state.metamask.selectedAddress, + } +} + +function mapDispatchToProps (dispatch) { + return { + toWyre: (address) => { + dispatch(actions.buyEth({ service: 'wyre', address, amount: 0 })) + }, + toCoinSwitch: (address) => { + dispatch(actions.buyEth({ service: 'coinswitch', address })) + }, + hideModal: () => { + dispatch(actions.hideModal()) + }, + hideWarning: () => { + dispatch(actions.hideWarning()) + }, + showAccountDetailModal: () => { + dispatch(actions.showModal({ name: 'ACCOUNT_DETAILS' })) + }, + toFaucet: network => dispatch(actions.buyEth({ network })), + } +} + +inherits(DepositEtherModal, Component) +function DepositEtherModal (_, context) { + Component.call(this) + + // need to set after i18n locale has loaded + DIRECT_DEPOSIT_ROW_TITLE = context.t('directDepositEther') + DIRECT_DEPOSIT_ROW_TEXT = context.t('directDepositEtherExplainer') + WYRE_ROW_TITLE = context.t('buyWithWyre') + WYRE_ROW_TEXT = context.t('buyWithWyreDescription') + FAUCET_ROW_TITLE = context.t('testFaucet') + COINSWITCH_ROW_TITLE = context.t('buyCoinSwitch') + COINSWITCH_ROW_TEXT = context.t('buyCoinSwitchExplainer') +} + +DepositEtherModal.contextTypes = { + t: PropTypes.func, +} + +module.exports = connect(mapStateToProps, mapDispatchToProps)(DepositEtherModal) + + +DepositEtherModal.prototype.facuetRowText = function (networkName) { + return this.context.t('getEtherFromFaucet', [networkName]) +} + +DepositEtherModal.prototype.renderRow = function ({ + logo, + title, + text, + buttonLabel, + onButtonClick, + hide, + className, + hideButton, + hideTitle, + onBackClick, + showBackButton, +}) { + if (hide) { + return null + } + + return h('div', { + className: className || 'deposit-ether-modal__buy-row', + }, [ + + onBackClick && showBackButton && h('div.deposit-ether-modal__buy-row__back', { + onClick: onBackClick, + }, [ + + h('i.fa.fa-arrow-left.cursor-pointer'), + + ]), + + h('div.deposit-ether-modal__buy-row__logo-container', [logo]), + + h('div.deposit-ether-modal__buy-row__description', [ + + !hideTitle && h('div.deposit-ether-modal__buy-row__description__title', [title]), + + h('div.deposit-ether-modal__buy-row__description__text', [text]), + + ]), + + !hideButton && h('div.deposit-ether-modal__buy-row__button', [ + h(Button, { + type: 'secondary', + className: 'deposit-ether-modal__deposit-button', + large: true, + onClick: onButtonClick, + }, [buttonLabel]), + ]), + + ]) +} + +DepositEtherModal.prototype.render = function () { + const { network, toWyre, toCoinSwitch, address, toFaucet } = this.props + + 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', {}, [ + + h('div.page-container__header', [ + + h('div.page-container__title', [this.context.t('depositEther')]), + + h('div.page-container__subtitle', [ + this.context.t('needEtherInWallet'), + ]), + + h('div.page-container__header-close', { + onClick: () => { + this.props.hideWarning() + this.props.hideModal() + }, + }), + + ]), + + h('.page-container__content', {}, [ + + h('div.deposit-ether-modal__buy-rows', [ + + this.renderRow({ + logo: h('img.deposit-ether-modal__logo', { + src: './images/deposit-eth.svg', + style: { + height: '75px', + width: '75px', + }, + }), + title: DIRECT_DEPOSIT_ROW_TITLE, + text: DIRECT_DEPOSIT_ROW_TEXT, + buttonLabel: this.context.t('viewAccount'), + onButtonClick: () => this.goToAccountDetailsModal(), + }), + + this.renderRow({ + logo: h('i.fa.fa-tint.fa-2x'), + title: FAUCET_ROW_TITLE, + text: this.facuetRowText(networkName), + buttonLabel: this.context.t('getEther'), + onButtonClick: () => toFaucet(network), + hide: !isTestNetwork, + }), + + this.renderRow({ + logo: h('div.deposit-ether-modal__logo', { + style: { + backgroundImage: 'url(\'./images/wyre.svg\')', + height: '40px', + }, + }), + title: WYRE_ROW_TITLE, + text: WYRE_ROW_TEXT, + buttonLabel: this.context.t('continueToWyre'), + onButtonClick: () => toWyre(address), + hide: isTestNetwork, + }), + + this.renderRow({ + logo: h('div.deposit-ether-modal__logo', { + style: { + backgroundImage: 'url(\'./images/coinswitch_logo.png\')', + height: '40px', + }, + }), + title: COINSWITCH_ROW_TITLE, + text: COINSWITCH_ROW_TEXT, + buttonLabel: this.context.t('continueToCoinSwitch'), + onButtonClick: () => toCoinSwitch(address), + hide: isTestNetwork, + }), + + ]), + + ]), + ]) +} + +DepositEtherModal.prototype.goToAccountDetailsModal = function () { + this.props.hideWarning() + this.props.hideModal() + this.props.showAccountDetailModal() +} diff --git a/ui/app/components/app/modals/deposit-ether-modal/deposit-ether-modal.component.js b/ui/app/components/app/modals/deposit-ether-modal/deposit-ether-modal.component.js deleted file mode 100644 index d503d950e..000000000 --- a/ui/app/components/app/modals/deposit-ether-modal/deposit-ether-modal.component.js +++ /dev/null @@ -1,168 +0,0 @@ -import PropTypes from 'prop-types' -import React, {Component} from 'react' -import {getNetworkDisplayName} from '../../../../../../app/scripts/controllers/network/util' -import Button from '../../../ui/button' - -export default class DepositEtherModal extends Component { - static contextTypes = { - t: PropTypes.func, - } - - static propTypes = { - network: PropTypes.string.isRequired, - toWyre: PropTypes.func.isRequired, - toCoinSwitch: PropTypes.func.isRequired, - address: PropTypes.string.isRequired, - toFaucet: PropTypes.func.isRequired, - hideWarning: PropTypes.func.isRequired, - hideModal: PropTypes.func.isRequired, - showAccountDetailModal: PropTypes.func.isRequired, - } - - faucetRowText = (networkName) => { - return this.context.t('getEtherFromFaucet', [networkName]) - } - - goToAccountDetailsModal = () => { - this.props.hideWarning() - this.props.hideModal() - this.props.showAccountDetailModal() - } - - renderRow ({ - logo, - title, - text, - buttonLabel, - onButtonClick, - hide, - className, - hideButton, - hideTitle, - onBackClick, - showBackButton, - }) { - if (hide) { - return null - } - - return ( -
    - {onBackClick && showBackButton && ( -
    - -
    - )} -
    {logo}
    -
    - {!hideTitle && ( -
    {title}
    - )} -
    {text}
    -
    - {!hideButton && ( -
    - -
    - )} -
    - ) - } - - render () { - const { network, toWyre, toCoinSwitch, address, toFaucet } = this.props - - const isTestNetwork = ['3', '4', '5', '42'].find(n => n === network) - const networkName = getNetworkDisplayName(network) - - return ( -
    -
    -
    - {this.context.t('depositEther')} -
    -
    - {this.context.t('needEtherInWallet')} -
    -
    { - this.props.hideWarning() - this.props.hideModal() - }} - /> -
    -
    -
    - {this.renderRow({ - logo: ( - - ), - title: this.context.t('directDepositEther'), - text: this.context.t('directDepositEtherExplainer'), - buttonLabel: this.context.t('viewAccount'), - onButtonClick: () => this.goToAccountDetailsModal(), - })} - {this.renderRow({ - logo: , - title: this.context.t('testFaucet'), - text: this.faucetRowText(networkName), - buttonLabel: this.context.t('getEther'), - onButtonClick: () => toFaucet(network), - hide: !isTestNetwork, - })} - {this.renderRow({ - logo: ( -
    - ), - title: this.context.t('buyWithWyre'), - text: this.context.t('buyWithWyreDescription'), - buttonLabel: this.context.t('continueToWyre'), - onButtonClick: () => toWyre(address), - hide: isTestNetwork, - })} - {this.renderRow({ - logo: ( -
    - ), - title: this.context.t('buyCoinSwitch'), - text: this.context.t('buyCoinSwitchExplainer'), - buttonLabel: this.context.t('continueToCoinSwitch'), - onButtonClick: () => toCoinSwitch(address), - hide: isTestNetwork, - })} -
    -
    -
    - ) - } -} diff --git a/ui/app/components/app/modals/deposit-ether-modal/deposit-ether-modal.container.js b/ui/app/components/app/modals/deposit-ether-modal/deposit-ether-modal.container.js deleted file mode 100644 index cccbe7667..000000000 --- a/ui/app/components/app/modals/deposit-ether-modal/deposit-ether-modal.container.js +++ /dev/null @@ -1,34 +0,0 @@ -import { connect } from 'react-redux' -import { buyEth, hideModal, showModal, hideWarning } from '../../../../store/actions' -import DepositEtherModal from './deposit-ether-modal.component' - -function mapStateToProps (state) { - return { - network: state.metamask.network, - address: state.metamask.selectedAddress, - } -} - -function mapDispatchToProps (dispatch) { - return { - toWyre: (address) => { - dispatch(buyEth({ service: 'wyre', address, amount: 0 })) - }, - toCoinSwitch: (address) => { - dispatch(buyEth({ service: 'coinswitch', address })) - }, - hideModal: () => { - dispatch(hideModal()) - }, - hideWarning: () => { - dispatch(hideWarning()) - }, - showAccountDetailModal: () => { - dispatch(showModal({ name: 'ACCOUNT_DETAILS' })) - }, - toFaucet: network => dispatch(buyEth({ network })), - } -} - - -export default connect(mapStateToProps, mapDispatchToProps)(DepositEtherModal) diff --git a/ui/app/components/app/modals/deposit-ether-modal/index.js b/ui/app/components/app/modals/deposit-ether-modal/index.js deleted file mode 100644 index 01a262a73..000000000 --- a/ui/app/components/app/modals/deposit-ether-modal/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './deposit-ether-modal.container' diff --git a/ui/app/components/app/modals/disconnect-account/disconnect-account.component.js b/ui/app/components/app/modals/disconnect-account/disconnect-account.component.js deleted file mode 100644 index 4fe5c7227..000000000 --- a/ui/app/components/app/modals/disconnect-account/disconnect-account.component.js +++ /dev/null @@ -1,52 +0,0 @@ -import React, { PureComponent } from 'react' -import PropTypes from 'prop-types' -import Modal from '../../modal' -import Button from '../../../ui/button' - - -export default class DisconnectAccount extends PureComponent { - static propTypes = { - hideModal: PropTypes.func.isRequired, - disconnectAccount: PropTypes.func.isRequired, - accountLabel: PropTypes.string.isRequired, - } - - static contextTypes = { - t: PropTypes.func, - } - - render () { - const { t } = this.context - const { hideModal, disconnectAccount, accountLabel } = this.props - - return ( - hideModal()} - hideFooter - > -
    -
    - { t('disconnectAccountModalDescription', [ accountLabel ]) } -
    - - -
    -
    - ) - } -} diff --git a/ui/app/components/app/modals/disconnect-account/disconnect-account.container.js b/ui/app/components/app/modals/disconnect-account/disconnect-account.container.js deleted file mode 100644 index b0511bb47..000000000 --- a/ui/app/components/app/modals/disconnect-account/disconnect-account.container.js +++ /dev/null @@ -1,44 +0,0 @@ -import { connect } from 'react-redux' -import { compose } from 'recompose' -import withModalProps from '../../../../helpers/higher-order-components/with-modal-props' -import DisconnectAccount from './disconnect-account.component' -import { getCurrentAccountWithSendEtherInfo } from '../../../../selectors/selectors' -import { removePermissionsFor } from '../../../../store/actions' - -const mapStateToProps = state => { - return { - ...state.appState.modal.modalState.props || {}, - accountLabel: getCurrentAccountWithSendEtherInfo(state).name, - } -} - -const mapDispatchToProps = dispatch => { - return { - disconnectAccount: (domainKey, domain) => { - const permissionMethodNames = domain.permissions.map(perm => perm.parentCapability) - dispatch(removePermissionsFor({ [domainKey]: permissionMethodNames })) - }, - } -} - -const mergeProps = (stateProps, dispatchProps, ownProps) => { - const { - domainKey, - domain, - } = stateProps - const { - disconnectAccount: dispatchDisconnectAccount, - } = dispatchProps - - return { - ...ownProps, - ...stateProps, - ...dispatchProps, - disconnectAccount: () => dispatchDisconnectAccount(domainKey, domain), - } -} - -export default compose( - withModalProps, - connect(mapStateToProps, mapDispatchToProps, mergeProps) -)(DisconnectAccount) diff --git a/ui/app/components/app/modals/disconnect-account/index.js b/ui/app/components/app/modals/disconnect-account/index.js deleted file mode 100644 index 43bfac9fd..000000000 --- a/ui/app/components/app/modals/disconnect-account/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './disconnect-account.container' diff --git a/ui/app/components/app/modals/disconnect-account/index.scss b/ui/app/components/app/modals/disconnect-account/index.scss deleted file mode 100644 index 861b7cec2..000000000 --- a/ui/app/components/app/modals/disconnect-account/index.scss +++ /dev/null @@ -1,25 +0,0 @@ -.disconnect-account-modal { - &__description { - color: #24292E; - margin-bottom: 24px; - } - - &__cancel-button { - border: none; - margin-top: 12px; - } -} - -.disconnect-account-modal-container { - .modal-container__header-text { - @extend %header--18; - } - - .modal-container__content { - padding-bottom: 18px; - - @media screen and (max-width: 575px) { - padding-bottom: 18px; - } - } -} \ No newline at end of file diff --git a/ui/app/components/app/modals/disconnect-all/disconnect-all.component.js b/ui/app/components/app/modals/disconnect-all/disconnect-all.component.js deleted file mode 100644 index 2d29fd9ea..000000000 --- a/ui/app/components/app/modals/disconnect-all/disconnect-all.component.js +++ /dev/null @@ -1,54 +0,0 @@ -import React, { PureComponent } from 'react' -import PropTypes from 'prop-types' -import Modal from '../../modal' -import Button from '../../../ui/button' -import { DEFAULT_ROUTE } from '../../../../helpers/constants/routes' - -export default class DisconnectAll extends PureComponent { - static propTypes = { - hideModal: PropTypes.func.isRequired, - disconnectAll: PropTypes.func.isRequired, - history: PropTypes.object.isRequired, - } - - static contextTypes = { - t: PropTypes.func, - } - - render () { - const { t } = this.context - const { hideModal, disconnectAll, history } = this.props - - return ( - hideModal()} - hideFooter - > -
    -
    - { t('disconnectAllModalDescription') } -
    - - -
    -
    - ) - } -} diff --git a/ui/app/components/app/modals/disconnect-all/index.js b/ui/app/components/app/modals/disconnect-all/index.js deleted file mode 100644 index 7fdfac530..000000000 --- a/ui/app/components/app/modals/disconnect-all/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './disconnect-all.container' diff --git a/ui/app/components/app/modals/disconnect-all/index.scss b/ui/app/components/app/modals/disconnect-all/index.scss deleted file mode 100644 index 8f69baade..000000000 --- a/ui/app/components/app/modals/disconnect-all/index.scss +++ /dev/null @@ -1,38 +0,0 @@ -.disconnect-all-modal { - height: 160px; - display: flex; - flex-direction: column; - justify-content: space-between; - - &__description { - color: #24292E; - margin-bottom: 24px; - } - - &__cancel-button { - border: none; - margin-top: 12px; - } - - .btn-secondary { - border: none; - } -} - -.disconnect-all-modal-container { - .modal-container__header-text { - font-family: Roboto; - font-style: normal; - font-weight: bold; - font-size: 18px; - color: #24292E; - } - - .modal-container__content { - padding-bottom: 18px; - - @media screen and (max-width: 575px) { - padding-bottom: 18px; - } - } -} diff --git a/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.component.js b/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.component.js index 661bbdefd..53ff473e4 100644 --- a/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.component.js +++ b/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.component.js @@ -76,22 +76,18 @@ export default class EditApprovalPermission extends PureComponent { className="edit-approval-permission__edit-section__radio-button" onClick={() => this.setState({ selectedOptionIsUnlimited: true })} > -
    +
    { selectedOptionIsUnlimited &&
    }
    -
    +
    { tokenAmount < tokenBalance ? t('proposedApprovalLimit') @@ -111,22 +107,18 @@ export default class EditApprovalPermission extends PureComponent { className="edit-approval-permission__edit-section__radio-button" onClick={() => this.setState({ selectedOptionIsUnlimited: false })} > -
    +
    { !selectedOptionIsUnlimited &&
    }
    -
    +
    { t('customSpendLimit') }
    diff --git a/ui/app/components/app/modals/export-private-key-modal.js b/ui/app/components/app/modals/export-private-key-modal.js new file mode 100644 index 000000000..1e1aaeb74 --- /dev/null +++ b/ui/app/components/app/modals/export-private-key-modal.js @@ -0,0 +1,178 @@ +const log = require('loglevel') +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 { stripHexPrefix } = require('ethereumjs-util') +const actions = require('../../../store/actions') +const AccountModalContainer = require('./account-modal-container') +const { getSelectedIdentity } = require('../../../selectors/selectors') +const ReadOnlyInput = require('../../ui/readonly-input') +const copyToClipboard = require('copy-to-clipboard') +const { checksumAddress } = require('../../../helpers/utils/util') +import Button from '../../ui/button' + +function mapStateToPropsFactory () { + let selectedIdentity = null + return function mapStateToProps (state) { + // We should **not** change the identity displayed here even if it changes from underneath us. + // If we do, we will be showing the user one private key and a **different** address and name. + // Note that the selected identity **will** change from underneath us when we unlock the keyring + // which is the expected behavior that we are side-stepping. + selectedIdentity = selectedIdentity || getSelectedIdentity(state) + return { + warning: state.appState.warning, + privateKey: state.appState.accountDetail.privateKey, + network: state.metamask.network, + selectedIdentity, + previousModalState: state.appState.modal.previousModalState.name, + } + } +} + +function mapDispatchToProps (dispatch) { + return { + exportAccount: (password, address) => { + return dispatch(actions.exportAccount(password, address)) + .then((res) => { + dispatch(actions.hideWarning()) + return res + }) + }, + showAccountDetailModal: () => dispatch(actions.showModal({ name: 'ACCOUNT_DETAILS' })), + hideModal: () => dispatch(actions.hideModal()), + } +} + +inherits(ExportPrivateKeyModal, Component) +function ExportPrivateKeyModal () { + Component.call(this) + + this.state = { + password: '', + privateKey: null, + showWarning: true, + } +} + +ExportPrivateKeyModal.contextTypes = { + t: PropTypes.func, +} + +module.exports = connect(mapStateToPropsFactory, mapDispatchToProps)(ExportPrivateKeyModal) + + +ExportPrivateKeyModal.prototype.exportAccountAndGetPrivateKey = function (password, address) { + const { exportAccount } = this.props + + exportAccount(password, address) + .then(privateKey => this.setState({ + privateKey, + showWarning: false, + })) + .catch((e) => log.error(e)) +} + +ExportPrivateKeyModal.prototype.renderPasswordLabel = function (privateKey) { + return h('span.private-key-password-label', privateKey + ? this.context.t('copyPrivateKey') + : this.context.t('typePassword') + ) +} + +ExportPrivateKeyModal.prototype.renderPasswordInput = function (privateKey) { + const plainKey = privateKey && stripHexPrefix(privateKey) + + return privateKey + ? h(ReadOnlyInput, { + wrapperClass: 'private-key-password-display-wrapper', + inputClass: 'private-key-password-display-textarea', + textarea: true, + value: plainKey, + onClick: () => copyToClipboard(plainKey), + }) + : h('input.private-key-password-input', { + type: 'password', + onChange: event => this.setState({ password: event.target.value }), + }) +} + +ExportPrivateKeyModal.prototype.renderButtons = function (privateKey, address, hideModal) { + return h('div.export-private-key-buttons', {}, [ + !privateKey && h(Button, { + type: 'default', + large: true, + className: 'export-private-key__button export-private-key__button--cancel', + onClick: () => hideModal(), + }, this.context.t('cancel')), + + (privateKey + ? ( + h(Button, { + type: 'secondary', + large: true, + className: 'export-private-key__button', + onClick: () => hideModal(), + }, this.context.t('done')) + ) : ( + h(Button, { + type: 'secondary', + large: true, + className: 'export-private-key__button', + disabled: !this.state.password, + onClick: () => this.exportAccountAndGetPrivateKey(this.state.password, address), + }, this.context.t('confirm')) + ) + ), + + ]) +} + +ExportPrivateKeyModal.prototype.render = function () { + const { + selectedIdentity, + warning, + showAccountDetailModal, + hideModal, + previousModalState, + } = this.props + const { name, address } = selectedIdentity + + const { + privateKey, + showWarning, + } = this.state + + return h(AccountModalContainer, { + selectedIdentity, + showBackButton: previousModalState === 'ACCOUNT_DETAILS', + backButtonAction: () => showAccountDetailModal(), + }, [ + + h('span.account-name', name), + + h(ReadOnlyInput, { + wrapperClass: 'ellip-address-wrapper', + inputClass: 'qr-ellip-address ellip-address', + value: checksumAddress(address), + }), + + h('div.account-modal-divider'), + + h('span.modal-body-title', this.context.t('showPrivateKeys')), + + h('div.private-key-password', {}, [ + this.renderPasswordLabel(privateKey), + + this.renderPasswordInput(privateKey), + + showWarning && warning ? h('span.private-key-password-error', warning) : null, + ]), + + h('div.private-key-password-warning', this.context.t('privateKeyWarning')), + + this.renderButtons(privateKey, address, hideModal), + + ]) +} diff --git a/ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.component.js b/ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.component.js deleted file mode 100644 index fd70cbc5b..000000000 --- a/ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.component.js +++ /dev/null @@ -1,168 +0,0 @@ -import log from 'loglevel' -import PropTypes from 'prop-types' -import React, { Component } from 'react' - -const { stripHexPrefix } = require('ethereumjs-util') -const copyToClipboard = require('copy-to-clipboard') -const { checksumAddress } = require('../../../../helpers/utils/util') -const ReadOnlyInput = require('../../../ui/readonly-input') -import Button from '../../../ui/button' -import AccountModalContainer from '../account-modal-container' - -export default class ExportPrivateKeyModal extends Component { - static contextTypes = { - t: PropTypes.func, - } - - static defaultProps = { - warning: null, - previousModalState: null, - } - - static propTypes = { - exportAccount: PropTypes.func.isRequired, - selectedIdentity: PropTypes.object.isRequired, - warning: PropTypes.node, - showAccountDetailModal: PropTypes.func.isRequired, - hideModal: PropTypes.func.isRequired, - previousModalState: PropTypes.string, - } - - state = { - password: '', - privateKey: null, - showWarning: true, - } - - exportAccountAndGetPrivateKey = (password, address) => { - const { exportAccount } = this.props - - exportAccount(password, address) - .then(privateKey => this.setState({ - privateKey, - showWarning: false, - })) - .catch((e) => log.error(e)) - } - - renderPasswordLabel (privateKey) { - return ( - - { - privateKey - ? this.context.t('copyPrivateKey') - : this.context.t('typePassword') - } - - ) - } - - renderPasswordInput (privateKey) { - const plainKey = privateKey && stripHexPrefix(privateKey) - - if (!privateKey) { - return ( - this.setState({ password: event.target.value })} - /> - ) - } - - return ( - copyToClipboard(plainKey)} - /> - ) - } - - renderButtons (privateKey, address, hideModal) { - return ( -
    - {!privateKey && ( - - )} - { - privateKey - ? ( - - ) - : ( - - ) - } -
    - ) - } - - render () { - const { - selectedIdentity, - warning, - showAccountDetailModal, - hideModal, - previousModalState, - } = this.props - const { name, address } = selectedIdentity - - const { - privateKey, - showWarning, - } = this.state - - return ( - showAccountDetailModal()} - > - {name} - -
    - {this.context.t('showPrivateKeys')} -
    - {this.renderPasswordLabel(privateKey)} - {this.renderPasswordInput(privateKey)} - { - (showWarning && warning) - ? {warning} - : null - } -
    -
    {this.context.t('privateKeyWarning')}
    - {this.renderButtons(privateKey, address, hideModal)} - - ) - } -} diff --git a/ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.container.js b/ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.container.js deleted file mode 100644 index 1b8f63c94..000000000 --- a/ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.container.js +++ /dev/null @@ -1,38 +0,0 @@ -import { connect } from 'react-redux' -import { exportAccount, hideWarning, showModal, hideModal } from '../../../../store/actions' -import { getSelectedIdentity } from '../../../../selectors/selectors' -import ExportPrivateKeyModal from './export-private-key-modal.component' - -function mapStateToPropsFactory () { - let selectedIdentity = null - return function mapStateToProps (state) { - // We should **not** change the identity displayed here even if it changes from underneath us. - // If we do, we will be showing the user one private key and a **different** address and name. - // Note that the selected identity **will** change from underneath us when we unlock the keyring - // which is the expected behavior that we are side-stepping. - selectedIdentity = selectedIdentity || getSelectedIdentity(state) - return { - warning: state.appState.warning, - privateKey: state.appState.accountDetail.privateKey, - network: state.metamask.network, - selectedIdentity, - previousModalState: state.appState.modal.previousModalState.name, - } - } -} - -function mapDispatchToProps (dispatch) { - return { - exportAccount: (password, address) => { - return dispatch(exportAccount(password, address)) - .then((res) => { - dispatch(hideWarning()) - return res - }) - }, - showAccountDetailModal: () => dispatch(showModal({ name: 'ACCOUNT_DETAILS' })), - hideModal: () => dispatch(hideModal()), - } -} - -export default connect(mapStateToPropsFactory, mapDispatchToProps)(ExportPrivateKeyModal) diff --git a/ui/app/components/app/modals/export-private-key-modal/index.js b/ui/app/components/app/modals/export-private-key-modal/index.js deleted file mode 100644 index 996c995ca..000000000 --- a/ui/app/components/app/modals/export-private-key-modal/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './export-private-key-modal.container' 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 c3709e923..e2b098923 100644 --- a/ui/app/components/app/modals/hide-token-confirmation-modal.js +++ b/ui/app/components/app/modals/hide-token-confirmation-modal.js @@ -1,5 +1,6 @@ -import PropTypes from 'prop-types' -import React, { Component } from 'react' +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') @@ -39,43 +40,44 @@ HideTokenConfirmationModal.contextTypes = { module.exports = connect(mapStateToProps, mapDispatchToProps)(HideTokenConfirmationModal) -HideTokenConfirmationModal.prototype.render = function HideTokenConfirmationModal () { +HideTokenConfirmationModal.prototype.render = function () { const { token, network, hideToken, hideModal, assetImages } = this.props const { symbol, address } = token const image = assetImages[address] - return ( -
    -
    -
    - {this.context.t('hideTokenPrompt')} -
    - -
    {symbol}
    -
    - {this.context.t('readdToken')} -
    -
    - - -
    -
    -
    - ) + return h('div.hide-token-confirmation', {}, [ + h('div.hide-token-confirmation__container', { + }, [ + h('div.hide-token-confirmation__title', {}, [ + this.context.t('hideTokenPrompt'), + ]), + + h(Identicon, { + className: 'hide-token-confirmation__identicon', + diameter: 45, + address, + network, + image, + }), + + h('div.hide-token-confirmation__symbol', {}, symbol), + + h('div.hide-token-confirmation__copy', {}, [ + this.context.t('readdToken'), + ]), + + h('div.hide-token-confirmation__buttons', {}, [ + h('button.btn-default.hide-token-confirmation__button.btn--large', { + onClick: () => hideModal(), + }, [ + this.context.t('cancel'), + ]), + 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/index.scss b/ui/app/components/app/modals/index.scss index dbf47265f..da7a27b84 100644 --- a/ui/app/components/app/modals/index.scss +++ b/ui/app/components/app/modals/index.scss @@ -11,9 +11,3 @@ @import './add-to-addressbook-modal/index'; @import './edit-approval-permission/index'; - -@import './disconnect-account/index'; - -@import './disconnect-all/index'; - -@import './new-account-modal/index'; diff --git a/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js b/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js index 9dc953315..6f3225382 100644 --- a/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js +++ b/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js @@ -75,8 +75,7 @@ export default class MetaMetricsOptInModal extends Component {
    - This data is aggregated and is therefore anonymous for the purposes of General Data Protection Regulation (EU) 2016/679. For more information in relation to our privacy practices, please see our  - , + contents: [ + h(DepositEtherModal, {}, []), + ], onHide: (props) => props.hideWarning(), mobileModalStyle: { width: '100%', @@ -115,88 +115,9 @@ const MODALS = { }, ADD_TO_ADDRESSBOOK: { - contents: , - mobileModalStyle: { - width: '95%', - top: '10%', - boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px', - transform: 'none', - left: '0', - right: '0', - margin: '0 auto', - borderRadius: '10px', - }, - laptopModalStyle: { - width: '375px', - top: '10%', - boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px', - transform: 'none', - left: '0', - right: '0', - margin: '0 auto', - borderRadius: '10px', - }, - contentStyle: { - borderRadius: '10px', - }, - }, - - NEW_ACCOUNT: { - contents: , - mobileModalStyle: { - width: '95%', - top: '10%', - boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px', - transform: 'none', - left: '0', - right: '0', - margin: '0 auto', - borderRadius: '10px', - }, - laptopModalStyle: { - width: '375px', - top: '10%', - boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px', - transform: 'none', - left: '0', - right: '0', - margin: '0 auto', - borderRadius: '10px', - }, - contentStyle: { - borderRadius: '10px', - }, - }, - - DISCONNECT_ACCOUNT: { - contents: , - mobileModalStyle: { - width: '95%', - top: '10%', - boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px', - transform: 'none', - left: '0', - right: '0', - margin: '0 auto', - borderRadius: '10px', - }, - laptopModalStyle: { - width: '375px', - top: '10%', - boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px', - transform: 'none', - left: '0', - right: '0', - margin: '0 auto', - borderRadius: '10px', - }, - contentStyle: { - borderRadius: '10px', - }, - }, - - DISCONNECT_ALL: { - contents: , + contents: [ + h(AddToAddressBookModal, {}, []), + ], mobileModalStyle: { width: '95%', top: '10%', @@ -223,17 +144,23 @@ const MODALS = { }, ACCOUNT_DETAILS: { - contents: , + contents: [ + h(AccountDetailsModal, {}, []), + ], ...accountModalStyle, }, EXPORT_PRIVATE_KEY: { - contents: , + contents: [ + h(ExportPrivateKeyModal, {}, []), + ], ...accountModalStyle, }, HIDE_TOKEN_CONFIRMATION: { - contents: , + contents: [ + h(HideTokenConfirmationModal, {}, []), + ], mobileModalStyle: { width: '95%', top: getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP ? '52vh' : '36.5vh', @@ -244,8 +171,21 @@ const MODALS = { }, }, + CLEAR_APPROVED_ORIGINS: { + contents: h(ClearApprovedOrigins), + mobileModalStyle: { + ...modalContainerMobileStyle, + }, + laptopModalStyle: { + ...modalContainerLaptopStyle, + }, + contentStyle: { + borderRadius: '8px', + }, + }, + METAMETRICS_OPT_IN_MODAL: { - contents: , + contents: h(MetaMetricsOptInModal), mobileModalStyle: { ...modalContainerMobileStyle, width: '100%', @@ -262,7 +202,12 @@ const MODALS = { }, GAS_PRICE_INFO_MODAL: { - contents: , + contents: [ + h(NotifcationModal, { + header: 'gasPriceNoDenom', + message: 'gasPriceInfoModalContent', + }), + ], mobileModalStyle: { width: '95%', top: getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP ? '52vh' : '36.5vh', @@ -274,7 +219,12 @@ const MODALS = { }, GAS_LIMIT_INFO_MODAL: { - contents: , + contents: [ + h(NotifcationModal, { + header: 'gasLimit', + message: 'gasLimitInfoModalContent', + }), + ], mobileModalStyle: { width: '95%', top: getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP ? '52vh' : '36.5vh', @@ -286,7 +236,7 @@ const MODALS = { }, CONFIRM_RESET_ACCOUNT: { - contents: , + contents: h(ConfirmResetAccount), mobileModalStyle: { ...modalContainerMobileStyle, }, @@ -299,7 +249,7 @@ const MODALS = { }, CONFIRM_REMOVE_ACCOUNT: { - contents: , + contents: h(ConfirmRemoveAccount), mobileModalStyle: { ...modalContainerMobileStyle, }, @@ -312,7 +262,7 @@ const MODALS = { }, CONFIRM_DELETE_NETWORK: { - contents: , + contents: h(ConfirmDeleteNetwork), mobileModalStyle: { ...modalContainerMobileStyle, }, @@ -325,7 +275,9 @@ const MODALS = { }, CUSTOMIZE_GAS: { - contents: , + contents: [ + h(ConfirmCustomizeGasModal), + ], mobileModalStyle: { width: '100vw', height: '100vh', @@ -354,7 +306,7 @@ const MODALS = { }, EDIT_APPROVAL_PERMISSION: { - contents: , + contents: h(EditApprovalPermission), mobileModalStyle: { width: '95vw', height: '100vh', @@ -380,7 +332,7 @@ const MODALS = { TRANSACTION_CONFIRMED: { disableBackdropClick: true, - contents: , + contents: h(TransactionConfirmed), mobileModalStyle: { ...modalContainerMobileStyle, }, @@ -393,7 +345,7 @@ const MODALS = { }, QR_SCANNER: { - contents: , + contents: h(QRScanner), mobileModalStyle: { ...modalContainerMobileStyle, }, @@ -406,7 +358,7 @@ const MODALS = { }, CANCEL_TRANSACTION: { - contents: , + contents: h(CancelTransaction), mobileModalStyle: { ...modalContainerMobileStyle, }, @@ -419,7 +371,7 @@ const MODALS = { }, REJECT_TRANSACTIONS: { - contents: , + contents: h(RejectTransactions), mobileModalStyle: { ...modalContainerMobileStyle, }, @@ -478,26 +430,25 @@ Modal.prototype.render = function () { const modalStyle = modal[isMobileView() ? 'mobileModalStyle' : 'laptopModalStyle'] const contentStyle = modal.contentStyle || {} - return ( - { + return h(FadeModal, + { + className: 'modal', + keyboard: false, + onHide: () => { if (modal.onHide) { modal.onHide(this.props) } this.onHide(modal.customOnHideOpts) - }} - ref={(ref) => { + }, + ref: (ref) => { this.modalRef = ref - }} - modalStyle={modalStyle} - contentStyle={contentStyle} - backdropStyle={BACKDROPSTYLE} - closeOnClick={!disableBackdropClick} - > - {children} - + }, + modalStyle, + contentStyle, + backdropStyle: BACKDROPSTYLE, + closeOnClick: !disableBackdropClick, + }, + children, ) } diff --git a/ui/app/components/app/modals/new-account-modal/index.js b/ui/app/components/app/modals/new-account-modal/index.js deleted file mode 100644 index 2c8b78890..000000000 --- a/ui/app/components/app/modals/new-account-modal/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './new-account-modal.container' diff --git a/ui/app/components/app/modals/new-account-modal/index.scss b/ui/app/components/app/modals/new-account-modal/index.scss deleted file mode 100644 index d6c2d0ac1..000000000 --- a/ui/app/components/app/modals/new-account-modal/index.scss +++ /dev/null @@ -1,37 +0,0 @@ -.new-account-modal { - @extend %col-nowrap; - @extend %modal; - - &__content { - @extend %col-nowrap; - padding: 1.5rem; - border-bottom: 1px solid $Grey-100; - - &__header { - @extend %h3; - } - } - - &__input-label { - color: $Grey-600; - margin-top: 1.25rem; - } - - &__input { - @extend %input; - margin-top: 0.75rem; - - &::placeholder { - color: $Grey-300; - } - } - - &__footer { - @extend %row-nowrap; - padding: 1rem; - - button + button { - margin-left: 1rem; - } - } -} diff --git a/ui/app/components/app/modals/new-account-modal/new-account-modal.component.js b/ui/app/components/app/modals/new-account-modal/new-account-modal.component.js deleted file mode 100644 index 26224ae63..000000000 --- a/ui/app/components/app/modals/new-account-modal/new-account-modal.component.js +++ /dev/null @@ -1,78 +0,0 @@ -import React, { Component } from 'react' -import PropTypes from 'prop-types' -import Button from '../../../ui/button/button.component' - -export default class NewAccountModal extends Component { - - static contextTypes = { - t: PropTypes.func, - } - - static propTypes = { - hideModal: PropTypes.func.isRequired, - newAccountNumber: PropTypes.number.isRequired, - onSave: PropTypes.func.isRequired, - } - - state = { - alias: '', - } - - onChange = e => { - this.setState({ - alias: e.target.value, - }) - } - - onSubmit = () => { - this.props.onSave(this.state.alias) - .then(this.props.hideModal) - } - - onKeyPress = e => { - if (e.key === 'Enter' && this.state.alias) { - this.onSubmit() - } - } - - render () { - const { t } = this.context - - return ( -
    -
    -
    - {t('newAccount')} -
    -
    - {t('accountName')} -
    - -
    -
    - - -
    -
    - ) - } -} diff --git a/ui/app/components/app/modals/new-account-modal/new-account-modal.container.js b/ui/app/components/app/modals/new-account-modal/new-account-modal.container.js deleted file mode 100644 index 812e98dbd..000000000 --- a/ui/app/components/app/modals/new-account-modal/new-account-modal.container.js +++ /dev/null @@ -1,44 +0,0 @@ -import { connect } from 'react-redux' -import NewAccountModal from './new-account-modal.component' -import actions from '../../../../store/actions' - -function mapStateToProps (state) { - return { - ...state.appState.modal.modalState.props || {}, - } -} - -function mapDispatchToProps (dispatch) { - return { - hideModal: () => dispatch(actions.hideModal()), - createAccount: newAccountName => { - return dispatch(actions.addNewAccount()) - .then(newAccountAddress => { - if (newAccountName) { - dispatch(actions.setAccountLabel(newAccountAddress, newAccountName)) - } - return newAccountAddress - }) - }, - } -} - -function mergeProps (stateProps, dispatchProps) { - const { - onCreateNewAccount, - } = stateProps - const { - createAccount, - } = dispatchProps - - return { - ...stateProps, - ...dispatchProps, - onSave: (newAccountName) => { - return createAccount(newAccountName) - .then(newAccountAddress => onCreateNewAccount(newAccountAddress)) - }, - } -} - -export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(NewAccountModal) diff --git a/ui/app/components/app/modals/notification-modal.js b/ui/app/components/app/modals/notification-modal.js index 800cd4f2f..84d9004b7 100644 --- a/ui/app/components/app/modals/notification-modal.js +++ b/ui/app/components/app/modals/notification-modal.js @@ -1,13 +1,10 @@ -import PropTypes from 'prop-types' -import React, {Component} from 'react' -import {connect} from 'react-redux' -import { hideModal } from '../../../store/actions' +const { Component } = require('react') +const PropTypes = require('prop-types') +const h = require('react-hyperscript') +const connect = require('react-redux').connect +const actions = require('../../../store/actions') class NotificationModal extends Component { - static contextProps = { - t: PropTypes.func.isRequired, - } - render () { const { header, @@ -18,48 +15,43 @@ class NotificationModal extends Component { onConfirm, } = this.props - const { t } = this.context - const showButtons = showCancelButton || showConfirmButton - return ( -
    -
    -
    - {this.context.t(header)} -
    -
    -
    - {this.context.t(message)} -
    -
    -
    - {showButtons && ( -
    - {showCancelButton && ( -
    - {t('cancel')} -
    - )} - {showConfirmButton && ( -
    { - onConfirm() - hideModal() - }} - > - {t('confirm')} -
    - )} -
    - )} -
    -
    - ) + return h('div', [ + h('div.notification-modal__wrapper', { + }, [ + + h('div.notification-modal__header', {}, [ + this.context.t(header), + ]), + + h('div.notification-modal__message-wrapper', {}, [ + h('div.notification-modal__message', {}, [ + this.context.t(message), + ]), + ]), + + h('div.modal-close-x', { + onClick: hideModal, + }), + + showButtons && h('div.notification-modal__buttons', [ + + showCancelButton && h('div.btn-default.notification-modal__buttons__btn', { + onClick: hideModal, + }, 'Cancel'), + + showConfirmButton && h('div.button.btn-secondary.notification-modal__buttons__btn', { + onClick: () => { + onConfirm() + hideModal() + }, + }, 'Confirm'), + + ]), + + ]), + ]) } } @@ -76,7 +68,7 @@ NotificationModal.propTypes = { const mapDispatchToProps = dispatch => { return { hideModal: () => { - dispatch(hideModal()) + dispatch(actions.hideModal()) }, } } diff --git a/ui/app/components/app/multiple-notifications/multiple-notifications.component.js b/ui/app/components/app/multiple-notifications/multiple-notifications.component.js index 59f540045..f9f6fe887 100644 --- a/ui/app/components/app/multiple-notifications/multiple-notifications.component.js +++ b/ui/app/components/app/multiple-notifications/multiple-notifications.component.js @@ -38,13 +38,9 @@ export default class MultipleNotifications extends PureComponent { className="home-notification-wrapper__i-container" onClick={() => this.setState({ showAll: !showAll })} > - {childrenToRender.length > 1 ? ( - - ) : null} + {childrenToRender.length > 1 ? : null}
    ) 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 1ecec3179..bcac637bd 100644 --- a/ui/app/components/app/network-display/network-display.component.js +++ b/ui/app/components/app/network-display/network-display.component.js @@ -38,15 +38,13 @@ export default class NetworkDisplay extends Component { return networkClass ?
    - : ( -
    - ) + :
    } render () { @@ -63,15 +61,13 @@ export default class NetworkDisplay extends Component { { networkClass ?
    - : ( -
    - ) + :
    }
    { type === 'rpc' && nickname ? nickname : this.context.t(type) } diff --git a/ui/app/components/app/network.js b/ui/app/components/app/network.js index 88204aef7..d46906a66 100644 --- a/ui/app/components/app/network.js +++ b/ui/app/components/app/network.js @@ -1,6 +1,7 @@ -import PropTypes from 'prop-types' -import React, {Component} from 'react' - +const Component = require('react').Component +const PropTypes = require('prop-types') +const h = require('react-hyperscript') +const connect = require('react-redux').connect const classnames = require('classnames') const inherits = require('util').inherits const NetworkDropdownIcon = require('./dropdowns/components/network-dropdown-icon') @@ -9,7 +10,8 @@ Network.contextTypes = { t: PropTypes.func, } -module.exports = Network +module.exports = connect()(Network) + inherits(Network, Component) @@ -17,14 +19,15 @@ function Network () { Component.call(this) } -Network.prototype.render = function Network () { +Network.prototype.render = function () { + const props = this.props const context = this.context - const networkNumber = this.props.network + const networkNumber = props.network let providerName, providerNick, providerUrl try { - providerName = this.props.provider.type - providerNick = this.props.provider.nickname || '' - providerUrl = this.props.provider.rpcTarget + providerName = props.provider.type + providerNick = props.provider.nickname || '' + providerUrl = props.provider.rpcTarget } catch (e) { providerName = null } @@ -53,124 +56,96 @@ Network.prototype.render = function Network () { } return ( -
    { + }), + title: hoverText, + onClick: (event) => { if (!this.props.disabled) { this.props.onClick(event) } - }} - > - {(function () { + }, + }, [ + (function () { switch (iconName) { case 'ethereum-network': - return ( -
    - -
    - {context.t('mainnet')} -
    -
    -
    - ) + return h('.network-indicator', [ + h(NetworkDropdownIcon, { + backgroundColor: '#038789', // $blue-lagoon + nonSelectBackgroundColor: '#15afb2', + loading: networkNumber === 'loading', + }), + h('.network-name', context.t('mainnet')), + h('.network-indicator__down-arrow'), + ]) case 'ropsten-test-network': - return ( -
    - -
    - {context.t('ropsten')} -
    -
    -
    - ) + return h('.network-indicator', [ + h(NetworkDropdownIcon, { + backgroundColor: '#e91550', // $crimson + nonSelectBackgroundColor: '#ec2c50', + loading: networkNumber === 'loading', + }), + h('.network-name', context.t('ropsten')), + h('.network-indicator__down-arrow'), + ]) case 'kovan-test-network': - return ( -
    - -
    - {context.t('kovan')} -
    -
    -
    - ) + return h('.network-indicator', [ + h(NetworkDropdownIcon, { + backgroundColor: '#690496', // $purple + nonSelectBackgroundColor: '#b039f3', + loading: networkNumber === 'loading', + }), + h('.network-name', context.t('kovan')), + h('.network-indicator__down-arrow'), + ]) case 'rinkeby-test-network': - return ( -
    - -
    - {context.t('rinkeby')} -
    -
    -
    - ) + return h('.network-indicator', [ + h(NetworkDropdownIcon, { + backgroundColor: '#ebb33f', // $tulip-tree + nonSelectBackgroundColor: '#ecb23e', + loading: networkNumber === 'loading', + }), + h('.network-name', context.t('rinkeby')), + h('.network-indicator__down-arrow'), + ]) case 'goerli-test-network': - return ( -
    - -
    {context.t('goerli')}
    -
    -
    - ) + return h('.network-indicator', [ + h(NetworkDropdownIcon, { + backgroundColor: '#3099f2', // $dodger-blue + nonSelectBackgroundColor: '#ecb23e', + loading: networkNumber === 'loading', + }), + h('.network-name', context.t('goerli')), + h('.network-indicator__down-arrow'), + ]) default: - return ( -
    - {networkNumber === 'loading' - ? ( - this.props.onClick(event)} - > - - - ) - : ( - - )} -
    - { - providerName === 'localhost' - ? context.t('localhost') - : providerNick || context.t('privateNetwork') - } -
    -
    -
    - ) + return h('.network-indicator', [ + networkNumber === 'loading' + ? h('span.pointer.network-loading-spinner', { + onClick: (event) => this.props.onClick(event), + }, [ + h('img', { + title: context.t('attemptingConnect'), + src: 'images/loading.svg', + }), + ]) + : h('i.fa.fa-question-circle.fa-lg', { + style: { + color: 'rgb(125, 128, 130)', + }, + }), + + h('.network-name', providerName === 'localhost' ? context.t('localhost') : providerNick || context.t('privateNetwork')), + h('.network-indicator__down-arrow'), + ]) } - })()} -
    + })(), + ]) ) } diff --git a/ui/app/components/app/permission-page-container/index.js b/ui/app/components/app/permission-page-container/index.js deleted file mode 100644 index ea3b8daaa..000000000 --- a/ui/app/components/app/permission-page-container/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export {default} from './permission-page-container.container' -export {default as PermissionPageContainerContent} from './permission-page-container-content' -export {default as PermissionPageContainerHeader} from './permission-page-container-header' diff --git a/ui/app/components/app/permission-page-container/index.scss b/ui/app/components/app/permission-page-container/index.scss deleted file mode 100644 index 7979867fa..000000000 --- a/ui/app/components/app/permission-page-container/index.scss +++ /dev/null @@ -1,281 +0,0 @@ -.permission-approval-container { - display: flex; - border: none; - box-shadow: none; - margin-top: 45px; - width: 466px; - min-height: 468px; - - &__header { - display: flex; - flex-direction: column; - align-items: flex-end; - border-bottom: 1px solid $geyser; - padding: 9px; - } - - &__title { - @extend %header--18; - line-height: 25px; - text-align: center; - position: fixed; - left: 0; - width: 100%; - } - - &__content { - display: flex; - overflow-y: auto; - flex: 1; - flex-direction: column; - color: #7C808E; - - &--redirect { - margin-top: 60px; - } - - h1, h2 { - color: #4A4A4A; - display: flex; - justify-content: center; - text-align: center; - } - - h2 { - font-size: 16px; - line-height: 18px; - padding: 20px; - } - - h1 { - font-size: 22px; - line-height: 26px; - padding: 20px; - } - - p { - padding: 0 40px; - text-align: center; - font-size: 12px; - line-height: 18px; - } - - a, a:hover { - color: $dodger-blue; - } - - section { - h1 { - padding: 30px 0px 0px 0px; - } - - h2 { - padding: 0px 0px 20px 0px; - } - } - - &__requested { - text-align: left; - } - - &__revoke-note { - margin-top: 24px; - } - - &__checkbox { - margin-right: 10px; - } - - &__permission { - margin-top: 18px; - - i { - color: #6A737D; - } - label { - margin-left: 6px; - color: #24292E; - } - } - - .permission-approval-visual { - display: flex; - flex-direction: row; - justify-content: space-evenly; - position: relative; - margin: 0 32px; - margin-top: 40px; - - section { - display: flex; - flex-direction: column; - align-items: center; - flex: 1; - } - - h1 { - font-size: 14px; - line-height: 18px; - padding: 8px 0 0; - } - - h2 { - font-size: 12px; - line-height: 17px; - color: #6A737D; - padding: 0; - } - - &__check { - width: 40px; - height: 40px; - background: white url("/images/permissions-check.svg") no-repeat; - margin-top: 24px; - z-index: 1; - } - - &__reject { - background: white; - z-index: 1; - display: flex; - justify-content: center; - align-items: center; - - i { - color: #D73A49; - transform: scale(3); - } - } - - &__broken-line { - z-index: 0; - position: absolute; - top: 43px; - } - - &__identicon, .icon-with-fallback__identicon { - width: 32px; - height: 32px; - z-index: 1; - - &--default { - background-color: #777A87; - color: white; - width: 64px; - height: 64px; - border-radius: 32px; - display: flex; - align-items: center; - justify-content: center; - font-weight: bold; - z-index: 1; - } - } - - &__identicon-container, .icon-with-fallback__identicon-container { - padding: 1rem; - flex: 1; - position: relative; - width: 100%; - display: flex; - justify-content: center; - align-items: center; - } - - &__identicon-border, .icon-with-fallback__identicon-border { - height: 64px; - width: 64px; - border-radius: 50%; - border: 1px solid white; - position: absolute; - background: #FFFFFF; - box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.25); - } - - &:before { - border-top: 2px dashed #CDD1E4; - content: ""; - margin: 0 auto; - position: absolute; - top: 32px; - left: 0; - bottom: 0; - right: 0; - width: 65%; - z-index: -1; - } - - &__account-info { - display: flex; - flex-direction: column; - align-items: center; - - &__label { - @extend %content-text; - line-height: 20px; - color: #000000; - } - - &__address { - @extend %font; - font-size: 12px; - line-height: 17px; - color: #6A737D; - } - } - } - - .secure-badge { - display: flex; - justify-content: center; - padding: 25px; - } - } - - &__permissions-header { - @extend %content-text; - line-height: 20px; - color: #6A737D; - - &--redirect { - text-align: center; - } - } - - &__permissions-container { - display: flex; - flex-direction: column; - margin-top: 33px; - } - - .page-container__footer { - border-top: none; - align-items: center; - - header { - width: 300px; - } - } - - &__permissions-header-redirect { - text-align: center; - } - - @media screen and (max-width: 575px) { - width: 100%; - margin-top: 25px; - padding: 10px; - - &__title { - position: initial; - } - - &__content-approval-visual { - margin-top: 16px; - } - - .page-container__footer header { - padding: 0; - } - } -} diff --git a/ui/app/components/app/permission-page-container/permission-page-container-content/index.js b/ui/app/components/app/permission-page-container/permission-page-container-content/index.js deleted file mode 100644 index 899d168f9..000000000 --- a/ui/app/components/app/permission-page-container/permission-page-container-content/index.js +++ /dev/null @@ -1 +0,0 @@ -export {default} from './permission-page-container-content.component' diff --git a/ui/app/components/app/permission-page-container/permission-page-container-content/permission-page-container-content.component.js b/ui/app/components/app/permission-page-container/permission-page-container-content/permission-page-container-content.component.js deleted file mode 100644 index d6a62dbbf..000000000 --- a/ui/app/components/app/permission-page-container/permission-page-container-content/permission-page-container-content.component.js +++ /dev/null @@ -1,163 +0,0 @@ -import PropTypes from 'prop-types' -import React, { PureComponent } from 'react' -import Identicon from '../../../ui/identicon' -import IconWithFallBack from '../../../ui/icon-with-fallback' -import classnames from 'classnames' - -export default class PermissionPageContainerContent extends PureComponent { - - static propTypes = { - requestMetadata: PropTypes.object.isRequired, - domainMetadata: PropTypes.object.isRequired, - selectedPermissions: PropTypes.object.isRequired, - permissionsDescriptions: PropTypes.object.isRequired, - onPermissionToggle: PropTypes.func.isRequired, - selectedAccount: PropTypes.object, - redirect: PropTypes.bool, - permissionRejected: PropTypes.bool, - } - - static defaultProps = { - redirect: null, - permissionRejected: null, - selectedAccount: {}, - } - - static contextTypes = { - t: PropTypes.func, - } - - renderAccountInfo = (account) => { - return ( -
    -
    - { account.label } -
    -
    - { account.truncatedAddress } -
    -
    - ) - } - - renderPermissionApprovalVisual = () => { - const { - requestMetadata, domainMetadata, selectedAccount, redirect, permissionRejected, - } = this.props - - return ( -
    -
    - - { redirect ? null :

    {domainMetadata.name}

    } - { redirect ? null :

    {requestMetadata.origin}

    } -
    - { permissionRejected - ? - : - } - -
    -
    -
    - -
    - { redirect ? null : this.renderAccountInfo(selectedAccount) } -
    -
    - ) - } - - renderRequestedPermissions () { - const { - selectedPermissions, permissionsDescriptions, onPermissionToggle, - } = this.props - const { t } = this.context - - const items = Object.keys(selectedPermissions).map((methodName) => { - - // the request will almost certainly be reject by rpc-cap if this happens - if (!permissionsDescriptions[methodName]) { - console.warn(`Unknown permission requested: ${methodName}`) - } - const description = permissionsDescriptions[methodName] || methodName - // don't allow deselecting eth_accounts - const isDisabled = methodName === 'eth_accounts' - - return ( -
    { - if (!isDisabled) { - onPermissionToggle(methodName) - } - }} - > - { selectedPermissions[methodName] - ? - : - } - -
    - ) - }) - - return ( -
    - {items} -
    { t('revokeInPermissions') }
    -
    - ) - } - - render () { - const { domainMetadata, redirect, permissionRejected } = this.props - const { t } = this.context - - let titleArgs - if (redirect && permissionRejected) { - titleArgs = [ 'cancelledConnectionWithMetaMask' ] - } else if (redirect) { - titleArgs = [ 'connectingWithMetaMask' ] - } else if (domainMetadata.extensionId) { - titleArgs = [ 'externalExtension', [domainMetadata.extensionId] ] - } else { - titleArgs = [ 'likeToConnect', [domainMetadata.name] ] - } - - return ( -
    -
    - { t(...titleArgs) } -
    - {this.renderPermissionApprovalVisual()} - { !redirect - ? ( -
    -
    - { domainMetadata.extensionId - ? t('thisWillAllowExternalExtension', [domainMetadata.extensionId]) - : t('thisWillAllow', [domainMetadata.name]) - } -
    - { this.renderRequestedPermissions() } -
    - ) - : ( -
    - { t('redirectingBackToDapp') } -
    - ) - } -
    - ) - } -} diff --git a/ui/app/components/app/permission-page-container/permission-page-container-header/index.js b/ui/app/components/app/permission-page-container/permission-page-container-header/index.js deleted file mode 100644 index 45ef9036b..000000000 --- a/ui/app/components/app/permission-page-container/permission-page-container-header/index.js +++ /dev/null @@ -1 +0,0 @@ -export {default} from './permission-page-container-header.component' diff --git a/ui/app/components/app/permission-page-container/permission-page-container.component.js b/ui/app/components/app/permission-page-container/permission-page-container.component.js deleted file mode 100644 index b7cbcd6ba..000000000 --- a/ui/app/components/app/permission-page-container/permission-page-container.component.js +++ /dev/null @@ -1,151 +0,0 @@ -import PropTypes from 'prop-types' -import React, { Component } from 'react' -import deepEqual from 'fast-deep-equal' -import { PermissionPageContainerContent } from '.' -import { PageContainerFooter } from '../../ui/page-container' - -export default class PermissionPageContainer extends Component { - - static propTypes = { - approvePermissionsRequest: PropTypes.func.isRequired, - rejectPermissionsRequest: PropTypes.func.isRequired, - selectedIdentity: PropTypes.object, - permissionsDescriptions: PropTypes.object.isRequired, - request: PropTypes.object, - redirect: PropTypes.bool, - permissionRejected: PropTypes.bool, - requestMetadata: PropTypes.object, - targetDomainMetadata: PropTypes.object.isRequired, - }; - - static defaultProps = { - redirect: null, - permissionRejected: null, - request: {}, - requestMetadata: {}, - selectedIdentity: {}, - }; - - static contextTypes = { - t: PropTypes.func, - metricsEvent: PropTypes.func, - }; - - state = { - selectedPermissions: this.getRequestedMethodState( - this.getRequestedMethodNames(this.props) - ), - } - - componentDidUpdate () { - const newMethodNames = this.getRequestedMethodNames(this.props) - - if (!deepEqual(Object.keys(this.state.selectedPermissions), newMethodNames)) { - // this should be a new request, so just overwrite - this.setState({ - selectedPermissions: this.getRequestedMethodState(newMethodNames), - }) - } - } - - getRequestedMethodState (methodNames) { - return methodNames.reduce( - (acc, methodName) => { - acc[methodName] = true - return acc - }, - {} - ) - } - - getRequestedMethodNames (props) { - return Object.keys(props.request.permissions || {}) - } - - onPermissionToggle = methodName => { - this.setState({ - selectedPermissions: { - ...this.state.selectedPermissions, - [methodName]: !this.state.selectedPermissions[methodName], - }, - }) - } - - componentDidMount () { - this.context.metricsEvent({ - eventOpts: { - category: 'Auth', - action: 'Connect', - name: 'Tab Opened', - }, - }) - } - - onCancel = () => { - const { request, rejectPermissionsRequest } = this.props - rejectPermissionsRequest(request.metadata.id) - } - - onSubmit = () => { - const { - request: _request, approvePermissionsRequest, rejectPermissionsRequest, selectedIdentity, - } = this.props - - const request = { - ..._request, - permissions: { ..._request.permissions }, - } - - Object.keys(this.state.selectedPermissions).forEach(key => { - if (!this.state.selectedPermissions[key]) { - delete request.permissions[key] - } - }) - - if (Object.keys(request.permissions).length > 0) { - approvePermissionsRequest(request, [selectedIdentity.address]) - } else { - rejectPermissionsRequest(request.metadata.id) - } - } - - render () { - const { - requestMetadata, - targetDomainMetadata, - permissionsDescriptions, - selectedIdentity, - redirect, - permissionRejected, - } = this.props - - return ( -
    - - { !redirect - ? ( - this.onCancel()} - cancelText={this.context.t('cancel')} - onSubmit={() => this.onSubmit()} - submitText={this.context.t('submit')} - submitButtonType="confirm" - buttonSizeLarge={false} - /> - ) - : null - } -
    - ) - } -} diff --git a/ui/app/components/app/permission-page-container/permission-page-container.container.js b/ui/app/components/app/permission-page-container/permission-page-container.container.js deleted file mode 100644 index f83393c70..000000000 --- a/ui/app/components/app/permission-page-container/permission-page-container.container.js +++ /dev/null @@ -1,28 +0,0 @@ -import { connect } from 'react-redux' -import { compose } from 'recompose' -import { withRouter } from 'react-router-dom' -import PermissionPageContainer from './permission-page-container.component' -import { - getPermissionsDescriptions, - getDomainMetadata, -} from '../../../selectors/selectors' - -const mapStateToProps = (state, ownProps) => { - const { request, cachedOrigin } = ownProps - const { metadata: requestMetadata = {} } = request || {} - - const domainMetadata = getDomainMetadata(state) - const origin = requestMetadata.origin || cachedOrigin - const targetDomainMetadata = (domainMetadata[origin] || { name: origin, icon: null }) - - return { - permissionsDescriptions: getPermissionsDescriptions(state), - requestMetadata, - targetDomainMetadata, - } -} - -export default compose( - withRouter, - connect(mapStateToProps) -)(PermissionPageContainer) diff --git a/ui/app/components/app/provider-page-container/index.js b/ui/app/components/app/provider-page-container/index.js new file mode 100644 index 000000000..927c35940 --- /dev/null +++ b/ui/app/components/app/provider-page-container/index.js @@ -0,0 +1,3 @@ +export {default} from './provider-page-container.component' +export {default as ProviderPageContainerContent} from './provider-page-container-content' +export {default as ProviderPageContainerHeader} from './provider-page-container-header' diff --git a/ui/app/components/app/provider-page-container/index.scss b/ui/app/components/app/provider-page-container/index.scss new file mode 100644 index 000000000..8d35ac179 --- /dev/null +++ b/ui/app/components/app/provider-page-container/index.scss @@ -0,0 +1,121 @@ +.provider-approval-container { + display: flex; + + &__header { + display: flex; + flex-direction: column; + align-items: flex-end; + border-bottom: 1px solid $geyser; + padding: 9px; + } + + &__content { + display: flex; + overflow-y: auto; + flex: 1; + flex-direction: column; + justify-content: space-between; + color: #7C808E; + + h1, h2 { + color: #4A4A4A; + display: flex; + justify-content: center; + text-align: center; + } + + h2 { + font-size: 16px; + line-height: 18px; + padding: 20px; + } + + h1 { + font-size: 22px; + line-height: 26px; + padding: 20px; + } + + p { + padding: 0 40px; + text-align: center; + font-size: 12px; + line-height: 18px; + } + + a, a:hover { + color: $dodger-blue; + } + + .provider-approval-visual { + display: flex; + flex-direction: row; + justify-content: space-evenly; + position: relative; + margin: 0 32px; + + section { + display: flex; + flex-direction: column; + align-items: center; + flex: 1; + } + + h1 { + font-size: 14px; + line-height: 18px; + padding: 8px 0 0; + } + + h2 { + font-size: 10px; + line-height: 14px; + padding: 0; + color: #A2A4AC; + } + + &__check { + width: 40px; + height: 40px; + background: white url("/images/provider-approval-check.svg") no-repeat; + margin-top: 14px; + } + + &__identicon { + width: 64px; + height: 64px; + + &--default { + background-color: #777A87; + color: white; + width: 64px; + height: 64px; + border-radius: 32px; + display: flex; + align-items: center; + justify-content: center; + font-weight: bold; + } + } + + &:before { + border-top: 2px dashed #CDD1E4; + content: ""; + margin: 0 auto; + position: absolute; + top: 32px; + left: 0; + bottom: 0; + right: 0; + width: 65%; + z-index: -1; + } + } + + .secure-badge { + display: flex; + justify-content: center; + padding: 25px; + } + } +} diff --git a/ui/app/components/app/provider-page-container/provider-page-container-content/index.js b/ui/app/components/app/provider-page-container/provider-page-container-content/index.js new file mode 100644 index 000000000..73e491adc --- /dev/null +++ b/ui/app/components/app/provider-page-container/provider-page-container-content/index.js @@ -0,0 +1 @@ +export {default} from './provider-page-container-content.container' diff --git a/ui/app/components/app/provider-page-container/provider-page-container-content/provider-page-container-content.component.js b/ui/app/components/app/provider-page-container/provider-page-container-content/provider-page-container-content.component.js new file mode 100644 index 000000000..4062b130f --- /dev/null +++ b/ui/app/components/app/provider-page-container/provider-page-container-content/provider-page-container-content.component.js @@ -0,0 +1,87 @@ +import PropTypes from 'prop-types' +import React, {PureComponent} from 'react' +import Identicon from '../../../ui/identicon' + +export default class ProviderPageContainerContent extends PureComponent { + static propTypes = { + origin: PropTypes.string.isRequired, + selectedIdentity: PropTypes.object.isRequired, + siteImage: PropTypes.string, + siteTitle: PropTypes.string, + hostname: PropTypes.string, + extensionId: PropTypes.string, + } + + static contextTypes = { + t: PropTypes.func, + }; + + renderConnectVisual = (title, identifier) => { + const { selectedIdentity, siteImage } = this.props + + return ( +
    +
    + {siteImage ? ( + + ) : ( + + {title.charAt(0).toUpperCase()} + + )} +

    {title}

    +

    {identifier}

    +
    + +
    + +

    {selectedIdentity.name}

    +
    +
    + ) + } + + render () { + const { siteTitle, hostname, extensionId } = this.props + const { t } = this.context + + const title = extensionId ? + 'External Extension' : + siteTitle || hostname + + const identifier = extensionId ? + `Extension ID: '${extensionId}'` : + hostname + + return ( +
    +
    +

    {t('connectRequest')}

    + {this.renderConnectVisual(title, identifier)} +

    {t('providerRequest', [title])}

    +

    + {t('providerRequestInfo')} +
    + + {t('learnMore')}. + +

    +
    +
    + +
    +
    + ) + } +} diff --git a/ui/app/components/app/provider-page-container/provider-page-container-content/provider-page-container-content.container.js b/ui/app/components/app/provider-page-container/provider-page-container-content/provider-page-container-content.container.js new file mode 100644 index 000000000..4dbdddd16 --- /dev/null +++ b/ui/app/components/app/provider-page-container/provider-page-container-content/provider-page-container-content.container.js @@ -0,0 +1,11 @@ +import { connect } from 'react-redux' +import ProviderPageContainerContent from './provider-page-container-content.component' +import { getSelectedIdentity } from '../../../../selectors/selectors' + +const mapStateToProps = (state) => { + return { + selectedIdentity: getSelectedIdentity(state), + } +} + +export default connect(mapStateToProps)(ProviderPageContainerContent) diff --git a/ui/app/components/app/provider-page-container/provider-page-container-header/index.js b/ui/app/components/app/provider-page-container/provider-page-container-header/index.js new file mode 100644 index 000000000..430627d3a --- /dev/null +++ b/ui/app/components/app/provider-page-container/provider-page-container-header/index.js @@ -0,0 +1 @@ +export {default} from './provider-page-container-header.component' diff --git a/ui/app/components/app/permission-page-container/permission-page-container-header/permission-page-container-header.component.js b/ui/app/components/app/provider-page-container/provider-page-container-header/provider-page-container-header.component.js similarity index 58% rename from ui/app/components/app/permission-page-container/permission-page-container-header/permission-page-container-header.component.js rename to ui/app/components/app/provider-page-container/provider-page-container-header/provider-page-container-header.component.js index 8ba3444ba..41bf6c3dd 100644 --- a/ui/app/components/app/permission-page-container/permission-page-container-header/permission-page-container-header.component.js +++ b/ui/app/components/app/provider-page-container/provider-page-container-header/provider-page-container-header.component.js @@ -1,10 +1,10 @@ import React, {PureComponent} from 'react' import NetworkDisplay from '../../network-display' -export default class PermissionPageContainerHeader extends PureComponent { +export default class ProviderPageContainerHeader extends PureComponent { render () { return ( -
    +
    ) 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 new file mode 100644 index 000000000..7d152e4cb --- /dev/null +++ b/ui/app/components/app/provider-page-container/provider-page-container.component.js @@ -0,0 +1,107 @@ +import PropTypes from 'prop-types' +import React, {PureComponent} from 'react' +import { ProviderPageContainerContent, ProviderPageContainerHeader } from '.' +import { PageContainerFooter } from '../../ui/page-container' +import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../../../app/scripts/lib/enums' +import { getEnvironmentType } from '../../../../../app/scripts/lib/util' + +export default class ProviderPageContainer extends PureComponent { + static propTypes = { + approveProviderRequestByOrigin: PropTypes.func.isRequired, + rejectProviderRequestByOrigin: PropTypes.func.isRequired, + origin: PropTypes.string.isRequired, + siteImage: PropTypes.string, + siteTitle: PropTypes.string, + hostname: PropTypes.string, + extensionId: PropTypes.string, + }; + + static contextTypes = { + t: PropTypes.func, + metricsEvent: PropTypes.func, + }; + + componentDidMount () { + if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION) { + window.addEventListener('beforeunload', this._beforeUnload) + } + this.context.metricsEvent({ + eventOpts: { + category: 'Auth', + action: 'Connect', + name: 'Popup Opened', + }, + }) + } + + _beforeUnload = () => { + const { origin, rejectProviderRequestByOrigin } = this.props + this.context.metricsEvent({ + eventOpts: { + category: 'Auth', + action: 'Connect', + name: 'Cancel Connect Request Via Notification Close', + }, + }) + this._removeBeforeUnload() + rejectProviderRequestByOrigin(origin) + } + + _removeBeforeUnload () { + window.removeEventListener('beforeunload', this._beforeUnload) + } + + componentWillUnmount () { + this._removeBeforeUnload() + } + + onCancel = () => { + const { origin, rejectProviderRequestByOrigin } = this.props + this.context.metricsEvent({ + eventOpts: { + category: 'Auth', + action: 'Connect', + name: 'Canceled', + }, + }) + this._removeBeforeUnload() + rejectProviderRequestByOrigin(origin) + } + + onSubmit = () => { + const { approveProviderRequestByOrigin, origin } = this.props + this.context.metricsEvent({ + eventOpts: { + category: 'Auth', + action: 'Connect', + name: 'Confirmed', + }, + }) + this._removeBeforeUnload() + approveProviderRequestByOrigin(origin) + } + + render () { + const {origin, siteImage, siteTitle, hostname, extensionId} = this.props + + return ( +
    + + + this.onCancel()} + cancelText={this.context.t('cancel')} + onSubmit={() => this.onSubmit()} + submitText={this.context.t('connect')} + submitButtonType="confirm" + /> +
    + ) + } +} diff --git a/ui/app/components/app/selected-account/tests/selected-account-component.test.js b/ui/app/components/app/selected-account/tests/selected-account-component.test.js index 42e5c8f1b..78a94d1c8 100644 --- a/ui/app/components/app/selected-account/tests/selected-account-component.test.js +++ b/ui/app/components/app/selected-account/tests/selected-account-component.test.js @@ -5,12 +5,10 @@ import SelectedAccount from '../selected-account.component' describe('SelectedAccount Component', () => { it('should render checksummed address', () => { - const wrapper = render(( - - ), { context: { t: () => {}}}) + const wrapper = render(, { context: { t: () => {}}}) // Checksummed version of address is displayed assert.equal(wrapper.find('.selected-account__address').text(), '0x1B82...5C9D') assert.equal(wrapper.find('.selected-account__name').text(), 'testName') diff --git a/ui/app/components/app/shift-list-item.js b/ui/app/components/app/shift-list-item.js new file mode 100644 index 000000000..f5fa00047 --- /dev/null +++ b/ui/app/components/app/shift-list-item.js @@ -0,0 +1,204 @@ +const inherits = require('util').inherits +const Component = require('react').Component +const PropTypes = require('prop-types') +const h = require('react-hyperscript') +const connect = require('react-redux').connect +const explorerLink = require('etherscan-link').createExplorerLink +const actions = require('../../store/actions') +const { formatDate, addressSummary } = require('../../helpers/utils/util') + +const CopyButton = require('../ui/copyButton') +const EthBalance = require('../ui/eth-balance') +const Tooltip = require('../ui/tooltip') + + +ShiftListItem.contextTypes = { + t: PropTypes.func, +} + +module.exports = connect(mapStateToProps)(ShiftListItem) + + +function mapStateToProps (state) { + return { + selectedAddress: state.metamask.selectedAddress, + conversionRate: state.metamask.conversionRate, + currentCurrency: state.metamask.currentCurrency, + } +} + +inherits(ShiftListItem, Component) + +function ShiftListItem () { + Component.call(this) +} + +ShiftListItem.prototype.render = function () { + return h('div.transaction-list-item.tx-list-clickable', { + style: { + paddingTop: '20px', + paddingBottom: '20px', + justifyContent: 'space-around', + alignItems: 'center', + flexDirection: 'row', + }, + }, [ + h('div', { + style: { + width: '0px', + position: 'relative', + bottom: '19px', + }, + }, [ + h('img', { + src: 'https://shapeshift.io/logo.png', + style: { + height: '35px', + width: '132px', + position: 'absolute', + clip: 'rect(0px,30px,34px,0px)', + }, + }), + ]), + + this.renderInfo(), + this.renderUtilComponents(), + ]) +} + +ShiftListItem.prototype.renderUtilComponents = function () { + var props = this.props + const { conversionRate, currentCurrency } = props + + switch (props.response.status) { + case 'no_deposits': + return h('.flex-row', [ + h(CopyButton, { + value: this.props.depositAddress, + }), + h(Tooltip, { + title: this.context.t('qrCode'), + }, [ + h('i.fa.fa-qrcode.pointer.pop-hover', { + onClick: () => props.dispatch(actions.reshowQrCode(props.depositAddress, props.depositType)), + style: { + margin: '5px', + marginLeft: '23px', + marginRight: '12px', + fontSize: '20px', + color: '#F7861C', + }, + }), + ]), + ]) + case 'received': + return h('.flex-row') + + case 'complete': + return h('.flex-row', [ + h(CopyButton, { + value: this.props.response.transaction, + }), + h(EthBalance, { + value: `${props.response.outgoingCoin}`, + conversionRate, + currentCurrency, + width: '55px', + shorten: true, + needsParse: false, + incoming: true, + style: { + fontSize: '15px', + color: '#01888C', + }, + }), + ]) + + case 'failed': + return '' + default: + return '' + } +} + +ShiftListItem.prototype.renderInfo = function () { + var props = this.props + switch (props.response.status) { + case 'no_deposits': + return h('.flex-column', { + style: { + overflow: 'hidden', + }, + }, [ + h('div', { + style: { + fontSize: 'x-small', + color: '#ABA9AA', + width: '100%', + }, + }, this.context.t('toETHviaShapeShift', [props.depositType])), + h('div', this.context.t('noDeposits')), + h('div', { + style: { + fontSize: 'x-small', + color: '#ABA9AA', + width: '100%', + }, + }, formatDate(props.time)), + ]) + case 'received': + return h('.flex-column', { + style: { + width: '200px', + overflow: 'hidden', + }, + }, [ + h('div', { + style: { + fontSize: 'x-small', + color: '#ABA9AA', + width: '100%', + }, + }, this.context.t('toETHviaShapeShift', [props.depositType])), + h('div', this.context.t('conversionProgress')), + h('div', { + style: { + fontSize: 'x-small', + color: '#ABA9AA', + width: '100%', + }, + }, formatDate(props.time)), + ]) + case 'complete': + var url = explorerLink(props.response.transaction, parseInt('1')) + + return h('.flex-column.pointer', { + style: { + width: '200px', + overflow: 'hidden', + }, + onClick: () => global.platform.openWindow({ url }), + }, [ + h('div', { + style: { + fontSize: 'x-small', + color: '#ABA9AA', + width: '100%', + }, + }, this.context.t('fromShapeShift')), + h('div', formatDate(props.time)), + h('div', { + style: { + fontSize: 'x-small', + color: '#ABA9AA', + width: '100%', + }, + }, addressSummary(props.response.transaction)), + ]) + + case 'failed': + return h('span.error', '(' + this.context.t('failed') + ')') + default: + return '' + } +} diff --git a/ui/app/components/app/shift-list-item/index.js b/ui/app/components/app/shift-list-item/index.js deleted file mode 100644 index fd6796983..000000000 --- a/ui/app/components/app/shift-list-item/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './shift-list-item.container' diff --git a/ui/app/components/app/shift-list-item/shift-list-item.component.js b/ui/app/components/app/shift-list-item/shift-list-item.component.js deleted file mode 100644 index 5e8797d27..000000000 --- a/ui/app/components/app/shift-list-item/shift-list-item.component.js +++ /dev/null @@ -1,240 +0,0 @@ -import PropTypes from 'prop-types' -import React, { Component } from 'react' -const explorerLink = require('etherscan-link').createExplorerLink -const actions = require('../../../store/actions') -const { formatDate, addressSummary } = require('../../../helpers/utils/util') - -const CopyButton = require('../../ui/copyButton') -const EthBalance = require('../../ui/eth-balance').default -const Tooltip = require('../../ui/tooltip') - -export default class ShiftListItem extends Component { - static contextTypes = { - t: PropTypes.func, - } - - static defaultProps = { - conversionRate: undefined, - currentCurrency: undefined, - } - - static propTypes = { - depositType: PropTypes.string.isRequired, - dispatch: PropTypes.func.isRequired, - depositAddress: PropTypes.string.isRequired, - conversionRate: PropTypes.any, - currentCurrency: PropTypes.any, - time: PropTypes.string.isRequired, - response: PropTypes.shape({ - outgoingCoin: PropTypes.number.isRequired, - status: PropTypes.string.isRequired, - transaction: PropTypes.string.isRequired, - }), - } - - renderUtilComponents () { - const { conversionRate, currentCurrency } = this.props - - switch (this.props.response.status) { - case 'no_deposits': - return ( -
    - - - { - this.props.dispatch(actions.reshowQrCode(this.props.depositAddress, this.props.depositType)) - }} - style={{ - margin: '5px', - marginLeft: '23px', - marginRight: '12px', - fontSize: '20px', - color: '#F7861C', - }} - /> - -
    - ) - case 'received': - return
    - - case 'complete': - return ( -
    - - -
    - ) - - case 'failed': - return '' - - default: - return '' - } - } - - renderInfo () { - switch (this.props.response.status) { - case 'no_deposits': - return ( -
    -
    - {this.context.t('toETHviaShapeShift', [this.props.depositType])} -
    -
    - {this.context.t('noDeposits')} -
    -
    - {formatDate(this.props.time)} -
    -
    - ) - - case 'received': - return ( -
    -
    - {this.context.t('toETHviaShapeShift', [this.props.depositType])} -
    -
    - {this.context.t('conversionProgress')} -
    -
    - {formatDate(this.props.time)} -
    -
    - ) - - case 'complete': - const url = explorerLink(this.props.response.transaction, parseInt('1')) - return ( -
    global.platform.openWindow({ url })} - > -
    - {this.context.t('fromShapeShift')} -
    -
    - {formatDate(this.props.time)} -
    -
    - {addressSummary(this.props.response.transaction)} -
    -
    - ) - - case 'failed': - return ( - - {`(${this.context.t('failed')})`} - - ) - - default: - return '' - } - } - - render () { - return ( -
    -
    - -
    - {this.renderInfo()} - {this.renderUtilComponents()} -
    - ) - } -} diff --git a/ui/app/components/app/shift-list-item/shift-list-item.container.js b/ui/app/components/app/shift-list-item/shift-list-item.container.js deleted file mode 100644 index 1d7645dfd..000000000 --- a/ui/app/components/app/shift-list-item/shift-list-item.container.js +++ /dev/null @@ -1,12 +0,0 @@ -import {connect} from 'react-redux' -import ShiftListItem from './shift-list-item.component' - -function mapStateToProps (state) { - return { - selectedAddress: state.metamask.selectedAddress, - conversionRate: state.metamask.conversionRate, - currentCurrency: state.metamask.currentCurrency, - } -} - -export default connect(mapStateToProps)(ShiftListItem) diff --git a/ui/app/components/app/sidebars/sidebar.component.js b/ui/app/components/app/sidebars/sidebar.component.js index 6437675c9..484b87e4b 100644 --- a/ui/app/components/app/sidebars/sidebar.component.js +++ b/ui/app/components/app/sidebars/sidebar.component.js @@ -20,15 +20,13 @@ export default class Sidebar extends Component { renderOverlay () { const { onOverlayClose } = this.props - return ( -
    { - onOverlayClose && onOverlayClose() - this.props.hideSidebar() - }} - /> - ) + return
    { + onOverlayClose && onOverlayClose() + this.props.hideSidebar() + } + } /> } renderSidebarContent () { diff --git a/ui/app/components/app/sidebars/tests/sidebars-component.test.js b/ui/app/components/app/sidebars/tests/sidebars-component.test.js index 3416ad8f6..5f6657dde 100644 --- a/ui/app/components/app/sidebars/tests/sidebars-component.test.js +++ b/ui/app/components/app/sidebars/tests/sidebars-component.test.js @@ -16,14 +16,12 @@ describe('Sidebar Component', function () { let wrapper beforeEach(() => { - wrapper = shallow(( - - )) + wrapper = shallow() }) afterEach(() => { diff --git a/ui/app/components/app/signature-request-original.js b/ui/app/components/app/signature-request-original.js new file mode 100644 index 000000000..e23d724cb --- /dev/null +++ b/ui/app/components/app/signature-request-original.js @@ -0,0 +1,357 @@ +const Component = require('react').Component +const PropTypes = require('prop-types') +const h = require('react-hyperscript') +const inherits = require('util').inherits +import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../../app/scripts/lib/enums' +import { getEnvironmentType } from '../../../../app/scripts/lib/util' +import Identicon from '../ui/identicon' +const connect = require('react-redux').connect +const ethUtil = require('ethereumjs-util') +const classnames = require('classnames') +const { compose } = require('recompose') +const { withRouter } = require('react-router-dom') +const { ObjectInspector } = require('react-inspector') + +import AccountListItem from '../../pages/send/account-list-item/account-list-item.component' + +const actions = require('../../store/actions') +const { conversionUtil } = require('../../helpers/utils/conversion-util') + +const { + getSelectedAccount, + getCurrentAccountWithSendEtherInfo, + getSelectedAddress, + conversionRateSelector, +} = require('../../selectors/selectors.js') + +import { clearConfirmTransaction } from '../../ducks/confirm-transaction/confirm-transaction.duck' +import Button from '../ui/button' + +const { DEFAULT_ROUTE } = require('../../helpers/constants/routes') + +function mapStateToProps (state) { + return { + balance: getSelectedAccount(state).balance, + selectedAccount: getCurrentAccountWithSendEtherInfo(state), + selectedAddress: getSelectedAddress(state), + requester: null, + requesterAddress: null, + conversionRate: conversionRateSelector(state), + } +} + +function mapDispatchToProps (dispatch) { + return { + goHome: () => dispatch(actions.goHome()), + clearConfirmTransaction: () => dispatch(clearConfirmTransaction()), + } +} + +function mergeProps (stateProps, dispatchProps, ownProps) { + const { + signPersonalMessage, + signTypedMessage, + cancelPersonalMessage, + cancelTypedMessage, + signMessage, + cancelMessage, + txData, + } = ownProps + + const { type } = txData + + let cancel + let sign + if (type === 'personal_sign') { + cancel = cancelPersonalMessage + sign = signPersonalMessage + } else if (type === 'eth_signTypedData') { + cancel = cancelTypedMessage + sign = signTypedMessage + } else if (type === 'eth_sign') { + cancel = cancelMessage + sign = signMessage + } + + return { + ...ownProps, + ...stateProps, + ...dispatchProps, + txData, + cancel, + sign, + } +} + +SignatureRequest.contextTypes = { + t: PropTypes.func, + metricsEvent: PropTypes.func, +} + +module.exports = compose( + withRouter, + connect(mapStateToProps, mapDispatchToProps, mergeProps) +)(SignatureRequest) + + +inherits(SignatureRequest, Component) +function SignatureRequest (props) { + Component.call(this) + + this.state = { + selectedAccount: props.selectedAccount, + } + this._beforeUnload = this._beforeUnload.bind(this) +} + +SignatureRequest.prototype._beforeUnload = function (event) { + const { clearConfirmTransaction, cancel } = this.props + const { metricsEvent } = this.context + metricsEvent({ + eventOpts: { + category: 'Transactions', + action: 'Sign Request', + name: 'Cancel Sig Request Via Notification Close', + }, + }) + clearConfirmTransaction() + cancel(event) +} + +SignatureRequest.prototype._removeBeforeUnload = function () { + if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION) { + window.removeEventListener('beforeunload', this._beforeUnload) + } +} + +SignatureRequest.prototype.componentDidMount = function () { + if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION) { + window.addEventListener('beforeunload', this._beforeUnload) + } +} + +SignatureRequest.prototype.componentWillUnmount = function () { + this._removeBeforeUnload() +} + +SignatureRequest.prototype.renderHeader = function () { + return h('div.request-signature__header', [ + + h('div.request-signature__header-background'), + + h('div.request-signature__header__text', this.context.t('sigRequest')), + + h('div.request-signature__header__tip-container', [ + h('div.request-signature__header__tip'), + ]), + + ]) +} + +SignatureRequest.prototype.renderAccount = function () { + const { selectedAccount } = this.state + + return h('div.request-signature__account', [ + + h('div.request-signature__account-text', [this.context.t('account') + ':']), + + h('div.request-signature__account-item', [ + h(AccountListItem, { + account: selectedAccount, + displayBalance: false, + }), + ]), + ]) +} + +SignatureRequest.prototype.renderBalance = function () { + const { balance, conversionRate } = this.props + + const balanceInEther = conversionUtil(balance, { + fromNumericBase: 'hex', + toNumericBase: 'dec', + fromDenomination: 'WEI', + numberOfDecimals: 6, + conversionRate, + }) + + return h('div.request-signature__balance', [ + + h('div.request-signature__balance-text', `${this.context.t('balance')}:`), + + h('div.request-signature__balance-value', `${balanceInEther} ETH`), + + ]) +} + +SignatureRequest.prototype.renderAccountInfo = function () { + return h('div.request-signature__account-info', [ + + this.renderAccount(), + + this.renderRequestIcon(), + + this.renderBalance(), + + ]) +} + +SignatureRequest.prototype.renderRequestIcon = function () { + const { requesterAddress } = this.props + + return h('div.request-signature__request-icon', [ + h(Identicon, { + diameter: 40, + address: requesterAddress, + }), + ]) +} + +SignatureRequest.prototype.renderRequestInfo = function () { + return h('div.request-signature__request-info', [ + + h('div.request-signature__headline', [ + this.context.t('yourSigRequested'), + ]), + + ]) +} + +SignatureRequest.prototype.msgHexToText = function (hex) { + try { + const stripped = ethUtil.stripHexPrefix(hex) + const buff = Buffer.from(stripped, 'hex') + return buff.length === 32 ? hex : buff.toString('utf8') + } catch (e) { + return hex + } +} + +// eslint-disable-next-line react/display-name +SignatureRequest.prototype.renderTypedData = function (data) { + const { domain, message } = JSON.parse(data) + return [ + h('div.request-signature__typed-container', [ + domain ? h('div', [ + h('h1', 'Domain'), + h(ObjectInspector, { data: domain, expandLevel: 1, name: 'domain' }), + ]) : '', + message ? h('div', [ + h('h1', 'Message'), + h(ObjectInspector, { data: message, expandLevel: 1, name: 'message' }), + ]) : '', + ]), + ] +} + +SignatureRequest.prototype.renderBody = function () { + let rows + let notice = this.context.t('youSign') + ':' + + const { txData } = this.props + const { type, msgParams: { data } } = txData + + if (type === 'personal_sign') { + rows = [{ name: this.context.t('message'), value: this.msgHexToText(data) }] + } else if (type === 'eth_signTypedData') { + rows = data + } else if (type === 'eth_sign') { + rows = [{ name: this.context.t('message'), value: data }] + notice = [this.context.t('signNotice'), + h('span.request-signature__help-link', { + onClick: () => { + global.platform.openWindow({ + url: 'https://metamask.zendesk.com/hc/en-us/articles/360015488751', + }) + }, + }, this.context.t('learnMore'))] + } + + return h('div.request-signature__body', {}, [ + + this.renderAccountInfo(), + + this.renderRequestInfo(), + + h('div.request-signature__notice', { + className: classnames({ + 'request-signature__notice': type === 'personal_sign' || type === 'eth_signTypedData', + 'request-signature__warning': type === 'eth_sign', + }), + }, [notice]), + + h('div.request-signature__rows', + rows.map(({ name, value }, index) => { + if (typeof value === 'boolean') { + value = value.toString() + } + return h('div.request-signature__row', { key: `request-signature-row-${index}` }, [ + h('div.request-signature__row-title', [`${name}:`]), + h('div.request-signature__row-value', value), + ]) + }) + ), + ]) +} + +SignatureRequest.prototype.renderFooter = function () { + const { cancel, sign } = this.props + + return h('div.request-signature__footer', [ + h(Button, { + type: 'default', + large: true, + className: 'request-signature__footer__cancel-button', + onClick: event => { + this._removeBeforeUnload() + cancel(event).then(() => { + this.context.metricsEvent({ + eventOpts: { + category: 'Transactions', + action: 'Sign Request', + name: 'Cancel', + }, + }) + this.props.clearConfirmTransaction() + this.props.history.push(DEFAULT_ROUTE) + }) + }, + }, this.context.t('cancel')), + h(Button, { + type: 'secondary', + large: true, + className: 'request-signature__footer__sign-button', + onClick: event => { + this._removeBeforeUnload() + sign(event).then(() => { + this.context.metricsEvent({ + eventOpts: { + category: 'Transactions', + action: 'Sign Request', + name: 'Confirm', + }, + }) + this.props.clearConfirmTransaction() + this.props.history.push(DEFAULT_ROUTE) + }) + }, + }, this.context.t('sign')), + ]) +} + +SignatureRequest.prototype.render = function () { + return ( + + h('div.request-signature__container', [ + + this.renderHeader(), + + this.renderBody(), + + this.renderFooter(), + + ]) + + ) + +} diff --git a/ui/app/components/app/signature-request-original/index.js b/ui/app/components/app/signature-request-original/index.js deleted file mode 100644 index 00a906785..000000000 --- a/ui/app/components/app/signature-request-original/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './signature-request-original.container' diff --git a/ui/app/components/app/signature-request-original/signature-request-original.component.js b/ui/app/components/app/signature-request-original/signature-request-original.component.js deleted file mode 100644 index 9a92ba5fb..000000000 --- a/ui/app/components/app/signature-request-original/signature-request-original.component.js +++ /dev/null @@ -1,324 +0,0 @@ -import React, { Component } from 'react' -import PropTypes from 'prop-types' -import ethUtil from 'ethereumjs-util' -import classnames from 'classnames' -import { ObjectInspector } from 'react-inspector' - -import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../../../app/scripts/lib/enums' -import { getEnvironmentType } from '../../../../../app/scripts/lib/util' -import Identicon from '../../ui/identicon' -import AccountListItem from '../../../pages/send/account-list-item/account-list-item.component' -import { conversionUtil } from '../../../helpers/utils/conversion-util' -import Button from '../../ui/button' -import { DEFAULT_ROUTE } from '../../../helpers/constants/routes' - -export default class SignatureRequestOriginal extends Component { - static contextTypes = { - t: PropTypes.func.isRequired, - metricsEvent: PropTypes.func.isRequired, - } - - static propTypes = { - balance: PropTypes.string, - cancel: PropTypes.func.isRequired, - clearConfirmTransaction: PropTypes.func.isRequired, - conversionRate: PropTypes.number, - history: PropTypes.object.isRequired, - requesterAddress: PropTypes.string, - selectedAccount: PropTypes.string, - sign: PropTypes.func.isRequired, - txData: PropTypes.object.isRequired, - } - - state = { - selectedAccount: this.props.selectedAccount, - } - - componentDidMount = () => { - if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION) { - window.addEventListener('beforeunload', this._beforeUnload) - } - } - - componentWillUnmount = () => { - this._removeBeforeUnload() - } - - _beforeUnload = (event) => { - const { clearConfirmTransaction, cancel } = this.props - const { metricsEvent } = this.context - metricsEvent({ - eventOpts: { - category: 'Transactions', - action: 'Sign Request', - name: 'Cancel Sig Request Via Notification Close', - }, - }) - clearConfirmTransaction() - cancel(event) - } - - _removeBeforeUnload = () => { - if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION) { - window.removeEventListener('beforeunload', this._beforeUnload) - } - } - - renderHeader = () => { - return ( -
    -
    - -
    - { this.context.t('sigRequest') } -
    - -
    -
    -
    -
    - ) - } - - renderAccount = () => { - const { selectedAccount } = this.state - - return ( -
    -
    - { `${this.context.t('account')}:` } -
    - -
    - -
    -
    - ) - } - - renderBalance = () => { - const { balance, conversionRate } = this.props - - const balanceInEther = conversionUtil(balance, { - fromNumericBase: 'hex', - toNumericBase: 'dec', - fromDenomination: 'WEI', - numberOfDecimals: 6, - conversionRate, - }) - - return ( -
    -
    - { `${this.context.t('balance')}:` } -
    -
    - { `${balanceInEther} ETH` } -
    -
    - ) - } - - renderRequestIcon = () => { - const { requesterAddress } = this.props - - return ( -
    - -
    - ) - } - - renderAccountInfo = () => { - return ( -
    - { this.renderAccount() } - { this.renderRequestIcon() } - { this.renderBalance() } -
    - ) - } - - renderRequestInfo = () => { - return ( -
    -
    - { this.context.t('yourSigRequested') } -
    -
    - ) - } - - msgHexToText = (hex) => { - try { - const stripped = ethUtil.stripHexPrefix(hex) - const buff = Buffer.from(stripped, 'hex') - return buff.length === 32 ? hex : buff.toString('utf8') - } catch (e) { - return hex - } - } - - renderTypedData = (data) => { - const { domain, message } = JSON.parse(data) - return ( -
    - { - domain - ? ( -
    -

    - Domain -

    - -
    - ) - : '' - } - { - message - ? ( -
    -

    - Message -

    - -
    - ) - : '' - } -
    - ) - } - - renderBody = () => { - let rows - let notice = `${this.context.t('youSign')}:` - - const { txData } = this.props - const { type, msgParams: { data } } = txData - - if (type === 'personal_sign') { - rows = [{ name: this.context.t('message'), value: this.msgHexToText(data) }] - } else if (type === 'eth_signTypedData') { - rows = data - } else if (type === 'eth_sign') { - rows = [{ name: this.context.t('message'), value: data }] - notice = this.context.t('signNotice') - } - - return ( -
    - { this.renderAccountInfo() } - { this.renderRequestInfo() } -
    - { notice } - { - type === 'eth_sign' - ? ( - { - global.platform.openWindow({ - url: 'https://metamask.zendesk.com/hc/en-us/articles/360015488751', - }) - }} - > - { this.context.t('learnMore') } - - ) - : null - } -
    -
    - { - rows.map(({ name, value }, index) => { - if (typeof value === 'boolean') { - value = value.toString() - } - return ( -
    -
    - { `${name}:` } -
    -
    - { value } -
    -
    - ) - }) - } -
    -
    - ) - } - - renderFooter = () => { - const { cancel, sign } = this.props - - return ( -
    - , - -
    - ) - } - - render = () => { - return ( -
    - { this.renderHeader() } - { this.renderBody() } - { this.renderFooter() } -
    - ) - } -} diff --git a/ui/app/components/app/signature-request-original/signature-request-original.container.js b/ui/app/components/app/signature-request-original/signature-request-original.container.js deleted file mode 100644 index be891b4db..000000000 --- a/ui/app/components/app/signature-request-original/signature-request-original.container.js +++ /dev/null @@ -1,72 +0,0 @@ -import { connect } from 'react-redux' -import { compose } from 'recompose' -import { withRouter } from 'react-router-dom' - -import actions from '../../../store/actions' -import { - getSelectedAccount, - getCurrentAccountWithSendEtherInfo, - getSelectedAddress, - conversionRateSelector, -} from '../../../selectors/selectors.js' -import { clearConfirmTransaction } from '../../../ducks/confirm-transaction/confirm-transaction.duck' -import SignatureRequestOriginal from './signature-request-original.component' - -function mapStateToProps (state) { - return { - balance: getSelectedAccount(state).balance, - selectedAccount: getCurrentAccountWithSendEtherInfo(state), - selectedAddress: getSelectedAddress(state), - requester: null, - requesterAddress: null, - conversionRate: conversionRateSelector(state), - } -} - -function mapDispatchToProps (dispatch) { - return { - goHome: () => dispatch(actions.goHome()), - clearConfirmTransaction: () => dispatch(clearConfirmTransaction()), - } -} - -function mergeProps (stateProps, dispatchProps, ownProps) { - const { - signPersonalMessage, - signTypedMessage, - cancelPersonalMessage, - cancelTypedMessage, - signMessage, - cancelMessage, - txData, - } = ownProps - - const { type } = txData - - let cancel - let sign - if (type === 'personal_sign') { - cancel = cancelPersonalMessage - sign = signPersonalMessage - } else if (type === 'eth_signTypedData') { - cancel = cancelTypedMessage - sign = signTypedMessage - } else if (type === 'eth_sign') { - cancel = cancelMessage - sign = signMessage - } - - return { - ...ownProps, - ...stateProps, - ...dispatchProps, - txData, - cancel, - sign, - } -} - -export default compose( - withRouter, - connect(mapStateToProps, mapDispatchToProps, mergeProps) -)(SignatureRequestOriginal) diff --git a/ui/app/components/app/signature-request/signature-request-header/signature-request-header.component.js b/ui/app/components/app/signature-request/signature-request-header/signature-request-header.component.js index 661a933a0..3ac0c9afb 100644 --- a/ui/app/components/app/signature-request/signature-request-header/signature-request-header.component.js +++ b/ui/app/components/app/signature-request/signature-request-header/signature-request-header.component.js @@ -14,12 +14,10 @@ export default class SignatureRequestHeader extends PureComponent { return (
    - {selectedAccount && ( - - )} + {selectedAccount && } {name}
    diff --git a/ui/app/components/app/signature-request/tests/signature-request.test.js b/ui/app/components/app/signature-request/tests/signature-request.test.js index 6ff07effc..68b114dd8 100644 --- a/ui/app/components/app/signature-request/tests/signature-request.test.js +++ b/ui/app/components/app/signature-request/tests/signature-request.test.js @@ -8,16 +8,11 @@ describe('Signature Request Component', function () { let wrapper beforeEach(() => { - wrapper = shallow(( - - )) + wrapper = shallow() }) describe('render', () => { diff --git a/ui/app/components/app/token-cell.js b/ui/app/components/app/token-cell.js new file mode 100644 index 000000000..495b9502b --- /dev/null +++ b/ui/app/components/app/token-cell.js @@ -0,0 +1,177 @@ +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 +import Identicon from '../ui/identicon' +const prefixForNetwork = require('../../../lib/etherscan-prefix-for-network') +const selectors = require('../../selectors/selectors') +const actions = require('../../store/actions') +const { conversionUtil, multiplyCurrencies } = require('../../helpers/utils/conversion-util') + +const TokenMenuDropdown = require('./dropdowns/token-menu-dropdown.js') + +function mapStateToProps (state) { + return { + network: state.metamask.network, + currentCurrency: state.metamask.currentCurrency, + selectedTokenAddress: state.metamask.selectedTokenAddress, + userAddress: selectors.getSelectedAddress(state), + contractExchangeRates: state.metamask.contractExchangeRates, + conversionRate: state.metamask.conversionRate, + sidebarOpen: state.appState.sidebar.isOpen, + } +} + +function mapDispatchToProps (dispatch) { + return { + setSelectedToken: address => dispatch(actions.setSelectedToken(address)), + hideSidebar: () => dispatch(actions.hideSidebar()), + } +} + +module.exports = connect(mapStateToProps, mapDispatchToProps)(TokenCell) + +inherits(TokenCell, Component) +function TokenCell () { + Component.call(this) + + this.state = { + tokenMenuOpen: false, + } +} + +TokenCell.contextTypes = { + metricsEvent: PropTypes.func, +} + +TokenCell.prototype.render = function () { + const { tokenMenuOpen } = this.state + const props = this.props + const { + address, + symbol, + string, + network, + setSelectedToken, + selectedTokenAddress, + contractExchangeRates, + conversionRate, + hideSidebar, + sidebarOpen, + currentCurrency, + // userAddress, + image, + } = props + let currentTokenToFiatRate + let currentTokenInFiat + let formattedFiat = '' + + if (contractExchangeRates[address]) { + currentTokenToFiatRate = multiplyCurrencies( + contractExchangeRates[address], + conversionRate + ) + currentTokenInFiat = conversionUtil(string, { + fromNumericBase: 'dec', + fromCurrency: symbol, + toCurrency: currentCurrency.toUpperCase(), + numberOfDecimals: 2, + conversionRate: currentTokenToFiatRate, + }) + formattedFiat = currentTokenInFiat.toString() === '0' + ? '' + : `${currentTokenInFiat} ${currentCurrency.toUpperCase()}` + } + + const showFiat = Boolean(currentTokenInFiat) && currentCurrency.toUpperCase() !== symbol + + return ( + h('div.token-list-item', { + className: `token-list-item ${selectedTokenAddress === address ? 'token-list-item--active' : ''}`, + // style: { cursor: network === '1' ? 'pointer' : 'default' }, + // onClick: this.view.bind(this, address, userAddress, network), + onClick: () => { + setSelectedToken(address) + this.context.metricsEvent({ + eventOpts: { + category: 'Navigation', + action: 'Token Menu', + name: 'Clicked Token', + }, + }) + selectedTokenAddress !== address && sidebarOpen && hideSidebar() + }, + }, [ + + h(Identicon, { + className: 'token-list-item__identicon', + diameter: 50, + address, + network, + image, + }), + + h('div.token-list-item__balance-ellipsis', null, [ + h('div.token-list-item__balance-wrapper', null, [ + h('div.token-list-item__token-balance', `${string || 0}`), + h('div.token-list-item__token-symbol', symbol), + showFiat && h('div.token-list-item__fiat-amount', { + style: {}, + }, formattedFiat), + ]), + + h('i.fa.fa-ellipsis-h.fa-lg.token-list-item__ellipsis.cursor-pointer', { + onClick: (e) => { + e.stopPropagation() + this.setState({ tokenMenuOpen: true }) + }, + }), + + ]), + + + tokenMenuOpen && h(TokenMenuDropdown, { + onClose: () => this.setState({ tokenMenuOpen: false }), + token: { symbol, address }, + }), + + /* + h('button', { + onClick: this.send.bind(this, address), + }, 'SEND'), + */ + + ]) + ) +} + +TokenCell.prototype.send = function (address, event) { + event.preventDefault() + event.stopPropagation() + const url = tokenFactoryFor(address) + if (url) { + navigateTo(url) + } +} + +TokenCell.prototype.view = function (address, userAddress, network) { + const url = etherscanLinkFor(address, userAddress, network) + if (url) { + navigateTo(url) + } +} + +function navigateTo (url) { + global.platform.openWindow({ url }) +} + +function etherscanLinkFor (tokenAddress, address, network) { + const prefix = prefixForNetwork(network) + return `https://${prefix}etherscan.io/token/${tokenAddress}?a=${address}` +} + +function tokenFactoryFor (tokenAddress) { + return `https://tokenfactory.surge.sh/#/token/${tokenAddress}` +} + diff --git a/ui/app/components/app/token-cell/index.js b/ui/app/components/app/token-cell/index.js deleted file mode 100644 index f0f3bcb4a..000000000 --- a/ui/app/components/app/token-cell/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './token-cell.container' diff --git a/ui/app/components/app/token-cell/token-cell.component.js b/ui/app/components/app/token-cell/token-cell.component.js deleted file mode 100644 index dc2bbb6d3..000000000 --- a/ui/app/components/app/token-cell/token-cell.component.js +++ /dev/null @@ -1,141 +0,0 @@ -import classnames from 'classnames' -import PropTypes from 'prop-types' -import React, { Component } from 'react' -import Identicon from '../../ui/identicon' -const prefixForNetwork = require('../../../../lib/etherscan-prefix-for-network') -const { conversionUtil, multiplyCurrencies } = require('../../../helpers/utils/conversion-util') - -const TokenMenuDropdown = require('../dropdowns/token-menu-dropdown.js') - -export default class TokenCell extends Component { - static contextTypes = { - metricsEvent: PropTypes.func, - } - - state = { - tokenMenuOpen: false, - } - - send (address, event) { - event.preventDefault() - event.stopPropagation() - const url = tokenFactoryFor(address) - if (url) { - navigateTo(url) - } - } - - view (address, userAddress, network) { - const url = etherscanLinkFor(address, userAddress, network) - if (url) { - navigateTo(url) - } - } - - render () { - const { tokenMenuOpen } = this.state - const props = this.props - const { - address, - symbol, - string, - network, - setSelectedToken, - selectedTokenAddress, - contractExchangeRates, - conversionRate, - hideSidebar, - sidebarOpen, - currentCurrency, - // userAddress, - image, - } = props - let currentTokenToFiatRate - let currentTokenInFiat - let formattedFiat = '' - - if (contractExchangeRates[address]) { - currentTokenToFiatRate = multiplyCurrencies( - contractExchangeRates[address], - conversionRate - ) - currentTokenInFiat = conversionUtil(string, { - fromNumericBase: 'dec', - fromCurrency: symbol, - toCurrency: currentCurrency.toUpperCase(), - numberOfDecimals: 2, - conversionRate: currentTokenToFiatRate, - }) - formattedFiat = currentTokenInFiat.toString() === '0' - ? '' - : `${currentTokenInFiat} ${currentCurrency.toUpperCase()}` - } - - const showFiat = Boolean(currentTokenInFiat) && currentCurrency.toUpperCase() !== symbol - - return ( -
    { - setSelectedToken(address) - this.context.metricsEvent({ - eventOpts: { - category: 'Navigation', - action: 'Token Menu', - name: 'Clicked Token', - }, - }) - selectedTokenAddress !== address && sidebarOpen && hideSidebar() - }} - > - -
    -
    -
    {string || 0}
    -
    {symbol}
    - {showFiat && ( -
    - {formattedFiat} -
    - )} -
    - { - e.stopPropagation() - this.setState({ tokenMenuOpen: true }) - }} - /> -
    - {tokenMenuOpen && ( - this.setState({ tokenMenuOpen: false })} - token={{ symbol, address }} - /> - )} -
    - ) - } -} - -function navigateTo (url) { - global.platform.openWindow({ url }) -} - -function etherscanLinkFor (tokenAddress, address, network) { - const prefix = prefixForNetwork(network) - return `https://${prefix}etherscan.io/token/${tokenAddress}?a=${address}` -} - -function tokenFactoryFor (tokenAddress) { - return `https://tokenfactory.surge.sh/#/token/${tokenAddress}` -} - diff --git a/ui/app/components/app/token-cell/token-cell.container.js b/ui/app/components/app/token-cell/token-cell.container.js deleted file mode 100644 index 176e93008..000000000 --- a/ui/app/components/app/token-cell/token-cell.container.js +++ /dev/null @@ -1,25 +0,0 @@ -import { connect } from 'react-redux' -import { setSelectedToken, hideSidebar } from '../../../store/actions' -import { getSelectedAddress } from '../../../selectors/selectors' -import TokenCell from './token-cell.component' - -function mapStateToProps (state) { - return { - network: state.metamask.network, - currentCurrency: state.metamask.currentCurrency, - selectedTokenAddress: state.metamask.selectedTokenAddress, - userAddress: getSelectedAddress(state), - contractExchangeRates: state.metamask.contractExchangeRates, - conversionRate: state.metamask.conversionRate, - sidebarOpen: state.appState.sidebar.isOpen, - } -} - -function mapDispatchToProps (dispatch) { - return { - setSelectedToken: address => dispatch(setSelectedToken(address)), - hideSidebar: () => dispatch(hideSidebar()), - } -} - -export default connect(mapStateToProps, mapDispatchToProps)(TokenCell) diff --git a/ui/app/components/app/token-list.js b/ui/app/components/app/token-list.js index ee2f85cac..000ca6b3f 100644 --- a/ui/app/components/app/token-list.js +++ b/ui/app/components/app/token-list.js @@ -1,8 +1,9 @@ -import PropTypes from 'prop-types' -import React, { Component } from 'react' -import TokenCell from './token-cell' +const Component = require('react').Component +const PropTypes = require('prop-types') +const h = require('react-hyperscript') const inherits = require('util').inherits const TokenTracker = require('eth-token-tracker') +const TokenCell = require('./token-cell.js') const connect = require('react-redux').connect const selectors = require('../../selectors/selectors') const log = require('loglevel') @@ -43,64 +44,53 @@ function TokenList () { Component.call(this) } -TokenList.prototype.render = function TokenList () { +TokenList.prototype.render = function () { const { userAddress, assetImages } = this.props const state = this.state const { tokens, isLoading, error } = state if (isLoading) { - return ( -
    - {this.context.t('loadingTokens')} -
    - ) + return this.message(this.context.t('loadingTokens')) } if (error) { log.error(error) - return ( -
    - {this.context.t('troubleTokenBalances')} - { - global.platform.openWindow({ - url: `https://ethplorer.io/address/${userAddress}`, - }) - }} - > - {this.context.t('here')} - -
    - ) + return h('.hotFix', { + style: { + padding: '80px', + }, + }, [ + this.context.t('troubleTokenBalances'), + h('span.hotFix', { + style: { + color: 'rgba(247, 134, 28, 1)', + cursor: 'pointer', + }, + onClick: () => { + global.platform.openWindow({ + url: `https://ethplorer.io/address/${userAddress}`, + }) + }, + }, this.context.t('here')), + ]) } - return ( -
    - {tokens.map((tokenData, index) => { - tokenData.image = assetImages[tokenData.address] - return ( - - ) - })} -
    - ) + return h('div', tokens.map((tokenData) => { + tokenData.image = assetImages[tokenData.address] + return h(TokenCell, tokenData) + })) + +} + +TokenList.prototype.message = function (body) { + return h('div', { + style: { + display: 'flex', + height: '250px', + alignItems: 'center', + justifyContent: 'center', + padding: '30px', + }, + }, body) } TokenList.prototype.componentDidMount = function () { @@ -115,9 +105,7 @@ TokenList.prototype.createFreshTokenTracker = function () { this.tracker.removeListener('error', this.showError) } - if (!global.ethereumProvider) { - return - } + if (!global.ethereumProvider) return const { userAddress } = this.props this.tracker = new TokenTracker({ @@ -166,9 +154,7 @@ TokenList.prototype.componentDidUpdate = function (prevProps) { const oldTokensLength = tokens ? tokens.length : 0 const tokensLengthUnchanged = oldTokensLength === newTokens.length - if (tokensLengthUnchanged && shouldUpdateTokens) { - return - } + if (tokensLengthUnchanged && shouldUpdateTokens) return this.setState({ isLoading: true }) this.createFreshTokenTracker() @@ -182,10 +168,21 @@ TokenList.prototype.updateBalances = function (tokens) { } TokenList.prototype.componentWillUnmount = function () { - if (!this.tracker) { - return - } + if (!this.tracker) return this.tracker.stop() this.tracker.removeListener('update', this.balanceUpdater) this.tracker.removeListener('error', this.showError) } + +// function uniqueMergeTokens (tokensA, tokensB = []) { +// const uniqueAddresses = [] +// const result = [] +// tokensA.concat(tokensB).forEach((token) => { +// const normal = normalizeAddress(token.address) +// if (!uniqueAddresses.includes(normal)) { +// uniqueAddresses.push(normal) +// result.push(token) +// } +// }) +// return result +// } diff --git a/ui/app/components/app/transaction-action/tests/transaction-action.component.test.js b/ui/app/components/app/transaction-action/tests/transaction-action.component.test.js index f97e81d15..16f2e256f 100644 --- a/ui/app/components/app/transaction-action/tests/transaction-action.component.test.js +++ b/ui/app/components/app/transaction-action/tests/transaction-action.component.test.js @@ -35,13 +35,11 @@ describe('TransactionAction Component', () => { }, } - const wrapper = shallow(( - - ), { context: { t }}) + const wrapper = shallow(, { context: { t }}) assert.equal(wrapper.find('.transaction-action').length, 1) wrapper.setState({ transactionAction: 'sentEther' }) diff --git a/ui/app/components/app/transaction-breakdown/transaction-breakdown.component.js b/ui/app/components/app/transaction-breakdown/transaction-breakdown.component.js index 0e1073337..5642e0fa5 100644 --- a/ui/app/components/app/transaction-breakdown/transaction-breakdown.component.js +++ b/ui/app/components/app/transaction-breakdown/transaction-breakdown.component.js @@ -50,12 +50,10 @@ export default class TransactionBreakdown extends PureComponent { className="transaction-breakdown__row-title" > {typeof gas !== 'undefined' - ? ( - - ) + ? : '?' } @@ -74,15 +72,13 @@ export default class TransactionBreakdown extends PureComponent { } {typeof gasPrice !== 'undefined' - ? ( - - ) + ? : '?' } 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 f3bd51b93..f27c74970 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 @@ -39,6 +39,7 @@ export default class TransactionListItemDetails extends PureComponent { state = { justCopied: false, + cancelDisabled: false, } handleEtherscanClick = () => { @@ -191,17 +192,15 @@ export default class TransactionListItemDetails extends PureComponent { { - showRetry && ( - - - - ) + showRetry && + + }
    diff --git a/ui/app/components/app/transaction-list-item/index.scss b/ui/app/components/app/transaction-list-item/index.scss index 9804ecd97..e0c62199e 100644 --- a/ui/app/components/app/transaction-list-item/index.scss +++ b/ui/app/components/app/transaction-list-item/index.scss @@ -139,7 +139,6 @@ &__expander { max-height: 0px; width: 100%; - overflow: hidden; &--show { max-height: 1000px; 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 3106f7fc3..9ab0105f9 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 @@ -236,22 +236,18 @@ export default class TransactionListItem extends PureComponent { )} /> { showEstimatedTime - ? ( - - ) + ? : null } { this.renderPrimaryCurrency() } { this.renderSecondaryCurrency() }
    -
    +
    { showTransactionDetails && (
    diff --git a/ui/app/components/app/transaction-view-balance/tests/token-view-balance.component.test.js b/ui/app/components/app/transaction-view-balance/tests/token-view-balance.component.test.js index d948355fc..0e2882e9c 100644 --- a/ui/app/components/app/transaction-view-balance/tests/token-view-balance.component.test.js +++ b/ui/app/components/app/transaction-view-balance/tests/token-view-balance.component.test.js @@ -25,16 +25,14 @@ describe('TransactionViewBalance Component', () => { }) it('should render ETH balance properly', () => { - const wrapper = shallow(( - - ), { context: { t, metricsEvent } }) + const wrapper = shallow(, { context: { t, metricsEvent } }) assert.equal(wrapper.find('.transaction-view-balance').length, 1) assert.equal(wrapper.find('.transaction-view-balance__button').length, 2) @@ -57,17 +55,15 @@ describe('TransactionViewBalance Component', () => { symbol: 'ABC', } - const wrapper = shallow(( - - ), { context: { t } }) + const wrapper = shallow(, { context: { t } }) assert.equal(wrapper.find('.transaction-view-balance').length, 1) assert.equal(wrapper.find('.transaction-view-balance__button').length, 1) diff --git a/ui/app/components/app/wallet-view.js b/ui/app/components/app/wallet-view.js new file mode 100644 index 000000000..55aeec333 --- /dev/null +++ b/ui/app/components/app/wallet-view.js @@ -0,0 +1,178 @@ +const Component = require('react').Component +const PropTypes = require('prop-types') +const connect = require('react-redux').connect +const h = require('react-hyperscript') +const { withRouter } = require('react-router-dom') +const { compose } = require('recompose') +const inherits = require('util').inherits +const { checksumAddress } = require('../../helpers/utils/util') +// const AccountDropdowns = require('./dropdowns/index.js').AccountDropdowns +const actions = require('../../store/actions') +import BalanceComponent from '../ui/balance' +const TokenList = require('./token-list') +const selectors = require('../../selectors/selectors') +const { ADD_TOKEN_ROUTE } = require('../../helpers/constants/routes') + +import AddTokenButton from './add-token-button' +import AccountDetails from './account-details' + +module.exports = compose( + withRouter, + connect(mapStateToProps, mapDispatchToProps) +)(WalletView) + +WalletView.contextTypes = { + t: PropTypes.func, + metricsEvent: PropTypes.func, +} + +WalletView.defaultProps = { + responsiveDisplayClassname: '', +} + +function mapStateToProps (state) { + + return { + network: state.metamask.network, + sidebarOpen: state.appState.sidebar.isOpen, + identities: state.metamask.identities, + accounts: selectors.getMetaMaskAccounts(state), + keyrings: state.metamask.keyrings, + selectedAddress: selectors.getSelectedAddress(state), + selectedAccount: selectors.getSelectedAccount(state), + selectedTokenAddress: state.metamask.selectedTokenAddress, + } +} + +function mapDispatchToProps (dispatch) { + return { + showSendPage: () => dispatch(actions.showSendPage()), + hideSidebar: () => dispatch(actions.hideSidebar()), + unsetSelectedToken: () => dispatch(actions.setSelectedToken()), + showAddTokenPage: () => dispatch(actions.showAddTokenPage()), + } +} + +inherits(WalletView, Component) +function WalletView () { + Component.call(this) +} + +WalletView.prototype.renderWalletBalance = function () { + const { + selectedTokenAddress, + selectedAccount, + unsetSelectedToken, + hideSidebar, + sidebarOpen, + } = this.props + + const selectedClass = selectedTokenAddress + ? '' + : 'wallet-balance-wrapper--active' + const className = `flex-column wallet-balance-wrapper ${selectedClass}` + + return h('div', { className }, [ + h('div.wallet-balance', + { + onClick: () => { + unsetSelectedToken() + selectedTokenAddress && sidebarOpen && hideSidebar() + }, + }, + [ + h(BalanceComponent, { + balanceValue: selectedAccount ? selectedAccount.balance : '', + style: {}, + }), + ] + ), + ]) +} + +WalletView.prototype.renderAddToken = function () { + const { + sidebarOpen, + hideSidebar, + history, + } = this.props + const { metricsEvent } = this.context + + return h(AddTokenButton, { + onClick () { + history.push(ADD_TOKEN_ROUTE) + metricsEvent({ + eventOpts: { + category: 'Navigation', + action: 'Token Menu', + name: 'Clicked "Add Token"', + }, + }) + if (sidebarOpen) { + hideSidebar() + } + }, + }) +} + +WalletView.prototype.render = function () { + const { + responsiveDisplayClassname, + selectedAddress, + keyrings, + identities, + network, + } = this.props + // temporary logs + fake extra wallets + + const checksummedAddress = checksumAddress(selectedAddress, network) + + if (!selectedAddress) { + throw new Error('selectedAddress should not be ' + String(selectedAddress)) + } + + const keyring = keyrings.find((kr) => { + return kr.accounts.includes(selectedAddress) + }) + + let label = '' + let type + if (keyring) { + type = keyring.type + if (type !== 'HD Key Tree') { + if (type.toLowerCase().search('hardware') !== -1) { + label = this.context.t('hardware') + } else { + label = this.context.t('imported') + } + } + } + + return h('div.wallet-view.flex-column', { + style: {}, + className: responsiveDisplayClassname, + }, [ + + h(AccountDetails, { + label, + checksummedAddress, + name: identities[selectedAddress].name, + }), + + this.renderWalletBalance(), + + h(TokenList), + + this.renderAddToken(), + ]) +} + +// TODO: Extra wallets, for dev testing. Remove when PRing to master. +// const extraWallet = h('div.flex-column.wallet-balance-wrapper', {}, [ +// h('div.wallet-balance', {}, [ +// h(BalanceComponent, { +// balanceValue: selectedAccount.balance, +// style: {}, +// }), +// ]), +// ]) diff --git a/ui/app/components/app/wallet-view/index.js b/ui/app/components/app/wallet-view/index.js deleted file mode 100644 index bc8fd26ab..000000000 --- a/ui/app/components/app/wallet-view/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './wallet-view.container' diff --git a/ui/app/components/app/wallet-view/wallet-view.component.js b/ui/app/components/app/wallet-view/wallet-view.component.js deleted file mode 100644 index dc3abf259..000000000 --- a/ui/app/components/app/wallet-view/wallet-view.component.js +++ /dev/null @@ -1,147 +0,0 @@ -import classnames from 'classnames' -import PropTypes from 'prop-types' -import React, { Component } from 'react' -import BalanceComponent from '../../ui/balance' -import AddTokenButton from '../add-token-button' -import AccountDetails from '../account-details' - -const { checksumAddress } = require('../../../helpers/utils/util') -const TokenList = require('../token-list') -const { ADD_TOKEN_ROUTE, CONNECTED_ROUTE } = require('../../../helpers/constants/routes') - -export default class WalletView extends Component { - static contextTypes = { - t: PropTypes.func, - metricsEvent: PropTypes.func, - } - - static defaultProps = { - responsiveDisplayClassname: '', - selectedAccount: null, - selectedTokenAddress: null, - } - - static propTypes = { - selectedTokenAddress: PropTypes.string, - selectedAccount: PropTypes.object, - selectedAddress: PropTypes.string.isRequired, - keyrings: PropTypes.array.isRequired, - responsiveDisplayClassname: PropTypes.string, - identities: PropTypes.object.isRequired, - history: PropTypes.object.isRequired, - unsetSelectedToken: PropTypes.func.isRequired, - sidebarOpen: PropTypes.bool.isRequired, - hideSidebar: PropTypes.func.isRequired, - } - - renderWalletBalance () { - const { - selectedTokenAddress, - selectedAccount, - unsetSelectedToken, - hideSidebar, - sidebarOpen, - } = this.props - - return ( -
    -
    { - unsetSelectedToken() - selectedTokenAddress && sidebarOpen && hideSidebar() - }} - > - -
    -
    - ) - } - - renderAddToken () { - const { - sidebarOpen, - hideSidebar, - history, - } = this.props - const { metricsEvent } = this.context - - return ( - { - history.push(ADD_TOKEN_ROUTE) - metricsEvent({ - eventOpts: { - category: 'Navigation', - action: 'Token Menu', - name: 'Clicked "Add Token"', - }, - }) - if (sidebarOpen) { - hideSidebar() - } - }} - /> - ) - } - - showConnectedSites = () => { - const { - sidebarOpen, - hideSidebar, - history, - } = this.props - history.push(CONNECTED_ROUTE) - if (sidebarOpen) { - hideSidebar() - } - } - - render () { - const { - responsiveDisplayClassname, - selectedAddress, - keyrings, - identities, - } = this.props - - const checksummedAddress = checksumAddress(selectedAddress) - - const keyring = keyrings.find((kr) => { - return kr.accounts.includes(selectedAddress) - }) - - let label = '' - let type - if (keyring) { - type = keyring.type - if (type !== 'HD Key Tree') { - if (type.toLowerCase().search('hardware') !== -1) { - label = this.context.t('hardware') - } else { - label = this.context.t('imported') - } - } - } - - return ( -
    - - {this.renderWalletBalance()} - - {this.renderAddToken()} -
    - ) - } -} diff --git a/ui/app/components/app/wallet-view/wallet-view.container.js b/ui/app/components/app/wallet-view/wallet-view.container.js deleted file mode 100644 index f0329e3c3..000000000 --- a/ui/app/components/app/wallet-view/wallet-view.container.js +++ /dev/null @@ -1,33 +0,0 @@ -import { connect } from 'react-redux' -import { withRouter } from 'react-router-dom' -import { compose } from 'recompose' -import WalletView from './wallet-view.component' -import {showSendPage, hideSidebar, setSelectedToken, showAddTokenPage} from '../../../store/actions' -import * as selectors from '../../../selectors/selectors' - -function mapStateToProps (state) { - return { - network: state.metamask.network, - sidebarOpen: state.appState.sidebar.isOpen, - identities: state.metamask.identities, - accounts: selectors.getMetaMaskAccounts(state), - keyrings: state.metamask.keyrings, - selectedAddress: selectors.getSelectedAddress(state), - selectedAccount: selectors.getSelectedAccount(state), - selectedTokenAddress: state.metamask.selectedTokenAddress, - } -} - -function mapDispatchToProps (dispatch) { - return { - showSendPage: () => dispatch(showSendPage()), - hideSidebar: () => dispatch(hideSidebar()), - unsetSelectedToken: () => dispatch(setSelectedToken()), - showAddTokenPage: () => dispatch(showAddTokenPage()), - } -} - -export default compose( - withRouter, - connect(mapStateToProps, mapDispatchToProps) -)(WalletView) diff --git a/ui/app/components/ui/alert/index.js b/ui/app/components/ui/alert/index.js index 7d09de3cb..da2ca4b66 100644 --- a/ui/app/components/ui/alert/index.js +++ b/ui/app/components/ui/alert/index.js @@ -1,12 +1,17 @@ -import classnames from 'classnames' -import PropTypes from 'prop-types' -import React, { Component } from 'react' +const { Component } = require('react') +const PropTypes = require('prop-types') +const h = require('react-hyperscript') class Alert extends Component { - state = { - visible: false, - msg: false, - className: '', + + constructor (props) { + super(props) + + this.state = { + visble: false, + msg: false, + className: '', + } } componentWillReceiveProps (nextProps) { @@ -21,14 +26,14 @@ class Alert extends Component { this.setState({ msg: props.msg, visible: true, - className: 'visible', + className: '.visible', }) } animateOut () { this.setState({ msg: null, - className: 'hidden', + className: '.hidden', }) setTimeout(_ => { @@ -40,9 +45,9 @@ class Alert extends Component { render () { if (this.state.visible) { return ( - + h(`div.global-alert${this.state.className}`, {}, + h('a.msg', {}, this.state.msg) + ) ) } return null diff --git a/ui/app/components/ui/button-group/button-group.stories.js b/ui/app/components/ui/button-group/button-group.stories.js index 1596680c1..c58c628b3 100644 --- a/ui/app/components/ui/button-group/button-group.stories.js +++ b/ui/app/components/ui/button-group/button-group.stories.js @@ -6,7 +6,7 @@ import Button from '../button' import { text, boolean } from '@storybook/addon-knobs/react' storiesOf('ButtonGroup', module) - .add('with Buttons', () => ( + .add('with Buttons', () => - )) - .add('with a disabled Button', () => ( + ) + .add('with a disabled Button', () => - )) + ) diff --git a/ui/app/components/ui/button-group/tests/button-group-component.test.js b/ui/app/components/ui/button-group/tests/button-group-component.test.js index b07b16cc9..663d86c74 100644 --- a/ui/app/components/ui/button-group/tests/button-group-component.test.js +++ b/ui/app/components/ui/button-group/tests/button-group-component.test.js @@ -21,16 +21,12 @@ describe('ButtonGroup Component', function () { let wrapper beforeEach(() => { - wrapper = shallow(( - - {mockButtons} - - )) + wrapper = shallow({mockButtons}) }) afterEach(() => { diff --git a/ui/app/components/ui/button/button.stories.js b/ui/app/components/ui/button/button.stories.js index 6540d5aa1..9df53439d 100644 --- a/ui/app/components/ui/button/button.stories.js +++ b/ui/app/components/ui/button/button.stories.js @@ -6,7 +6,7 @@ import { text, boolean } from '@storybook/addon-knobs/react' // ', 'secondary', 'default', 'warning', 'danger', 'danger-primary', 'link'], 'primary')} storiesOf('Button', module) - .add('Button - Primary', () => ( + .add('Button - Primary', () => - )) - .add('Button - Secondary', () => ( + ) + .add('Button - Secondary', () => - )) - .add('Button - Default', () => ( + ) + .add('Button - Default', () => - )) - .add('Button - Warning', () => ( + ) + .add('Button - Warning', () => - )) - .add('Button - Danger', () => ( + ) + .add('Button - Danger', () => - )) - .add('Button - Danger Primary', () => ( + ) + .add('Button - Danger Primary', () => - )) - .add('Button - Link', () => ( + ) + .add('Button - Link', () => - )) + ) diff --git a/ui/app/components/ui/copyButton.js b/ui/app/components/ui/copyButton.js index d235fea04..a60d33523 100644 --- a/ui/app/components/ui/copyButton.js +++ b/ui/app/components/ui/copyButton.js @@ -1,66 +1,66 @@ -import React, { Component } from 'react' -import PropTypes from 'prop-types' -import { connect } from 'react-redux' - +const Component = require('react').Component +const PropTypes = require('prop-types') +const h = require('react-hyperscript') +const inherits = require('util').inherits const copyToClipboard = require('copy-to-clipboard') +const connect = require('react-redux').connect + const Tooltip = require('./tooltip') -class CopyButton extends Component { - static contextTypes = { - t: PropTypes.func, - } - - static defaultProps = { - title: null, - } - - static propTypes = { - value: PropTypes.string.isRequired, - title: PropTypes.string, - } - - state = {} - - debounceRestore = () => { - this.setState({ copied: true }) - clearTimeout(this.timeout) - this.timeout = setTimeout(() => { - this.setState({ copied: false }) - }, 850) - } - - render () { - const state = this.state - const props = this.props - const value = props.value - const copied = state.copied - const message = copied ? this.context.t('copiedButton') : props.title || this.context.t('copyButton') - - return ( -
    - - { - event.preventDefault() - event.stopPropagation() - copyToClipboard(value) - this.debounceRestore() - }} - /> - -
    - ) - } +CopyButton.contextTypes = { + t: PropTypes.func, } module.exports = connect()(CopyButton) + + +inherits(CopyButton, Component) +function CopyButton () { + Component.call(this) +} + +// As parameters, accepts: +// "value", which is the value to copy (mandatory) +// "title", which is the text to show on hover (optional, defaults to 'Copy') +CopyButton.prototype.render = function () { + const props = this.props + const state = this.state || {} + + const value = props.value + const copied = state.copied + + const message = copied ? this.context.t('copiedButton') : props.title || this.context.t('copyButton') + + return h('.copy-button', { + style: { + display: 'flex', + alignItems: 'center', + }, + }, [ + + h(Tooltip, { + title: message, + }, [ + h('i.fa.fa-clipboard.cursor-pointer.color-orange', { + style: { + margin: '5px', + }, + onClick: (event) => { + event.preventDefault() + event.stopPropagation() + copyToClipboard(value) + this.debounceRestore() + }, + }), + ]), + + ]) +} + +CopyButton.prototype.debounceRestore = function () { + this.setState({ copied: true }) + clearTimeout(this.timeout) + this.timeout = setTimeout(() => { + this.setState({ copied: false }) + }, 850) +} diff --git a/ui/app/components/ui/currency-display/tests/currency-display.component.test.js b/ui/app/components/ui/currency-display/tests/currency-display.component.test.js index 387493da6..d9ef052f1 100644 --- a/ui/app/components/ui/currency-display/tests/currency-display.component.test.js +++ b/ui/app/components/ui/currency-display/tests/currency-display.component.test.js @@ -5,25 +5,21 @@ import CurrencyDisplay from '../currency-display.component' describe('CurrencyDisplay Component', () => { it('should render text with a className', () => { - const wrapper = shallow(( - - )) + const wrapper = shallow() assert.ok(wrapper.hasClass('currency-display')) assert.equal(wrapper.text(), '$123.45') }) it('should render text with a prefix', () => { - const wrapper = shallow(( - - )) + const wrapper = shallow() assert.ok(wrapper.hasClass('currency-display')) assert.equal(wrapper.text(), '-$123.45') diff --git a/ui/app/components/ui/editable-label.js b/ui/app/components/ui/editable-label.js index 43b288819..8eb10e174 100644 --- a/ui/app/components/ui/editable-label.js +++ b/ui/app/components/ui/editable-label.js @@ -1,17 +1,16 @@ -import classnames from 'classnames' -import PropTypes from 'prop-types' -import React, { Component } from 'react' +const { Component } = require('react') +const PropTypes = require('prop-types') +const h = require('react-hyperscript') +const classnames = require('classnames') class EditableLabel extends Component { - static propTypes = { - onSubmit: PropTypes.func.isRequired, - defaultValue: PropTypes.string, - className: PropTypes.string, - } + constructor (props) { + super(props) - state = { - isEditing: false, - value: this.props.defaultValue || '', + this.state = { + isEditing: false, + value: props.defaultValue || '', + } } handleSubmit () { @@ -25,41 +24,46 @@ class EditableLabel extends Component { .then(() => this.setState({ isEditing: false })) } + saveIfEnter (event) { + if (event.key === 'Enter') { + this.handleSubmit() + } + } + renderEditing () { const { value } = this.state - return [( - { + return ([ + h('input.large-input.editable-label__input', { + type: 'text', + required: true, + dir: 'auto', + value: this.state.value, + onKeyPress: (event) => { if (event.key === 'Enter') { this.handleSubmit() } - }} - onChange={event => this.setState({ value: event.target.value })} - className={classnames('large-input', 'editable-label__input', { - 'editable-label__input--error': value === '', - })} - /> - ), ( -
    - this.handleSubmit()} /> -
    - )] + }, + onChange: event => this.setState({ value: event.target.value }), + className: classnames({ 'editable-label__input--error': value === '' }), + }), + h('div.editable-label__icon-wrapper', [ + h('i.fa.fa-check.editable-label__icon', { + onClick: () => this.handleSubmit(), + }), + ]), + ]) } renderReadonly () { - return [( -
    {this.state.value}
    - ), ( -
    - this.setState({ isEditing: true })} /> -
    - )] + return ([ + h('div.editable-label__value', this.state.value), + h('div.editable-label__icon-wrapper', [ + h('i.fa.fa-pencil.editable-label__icon', { + onClick: () => this.setState({ isEditing: true }), + }), + ]), + ]) } render () { @@ -67,15 +71,19 @@ class EditableLabel extends Component { const { className } = this.props return ( -
    - { - isEditing - ? this.renderEditing() - : this.renderReadonly() - } -
    + h('div.editable-label', { className: classnames(className) }, + isEditing + ? this.renderEditing() + : this.renderReadonly() + ) ) } } +EditableLabel.propTypes = { + onSubmit: PropTypes.func.isRequired, + defaultValue: PropTypes.string, + className: PropTypes.string, +} + module.exports = EditableLabel diff --git a/ui/app/components/ui/eth-balance.js b/ui/app/components/ui/eth-balance.js new file mode 100644 index 000000000..7d577b716 --- /dev/null +++ b/ui/app/components/ui/eth-balance.js @@ -0,0 +1,102 @@ +const { Component } = require('react') +const h = require('react-hyperscript') +const connect = require('react-redux').connect +const { inherits } = require('util') +const { + formatBalance, + generateBalanceObject, +} = require('../../helpers/utils/util') +const Tooltip = require('./tooltip.js') +const FiatValue = require('./fiat-value.js') + +module.exports = connect(mapStateToProps)(EthBalanceComponent) +function mapStateToProps (state) { + return { + ticker: state.metamask.ticker, + } +} + +inherits(EthBalanceComponent, Component) +function EthBalanceComponent () { + Component.call(this) +} + +EthBalanceComponent.prototype.render = function () { + const props = this.props + const { ticker, value, style, width, needsParse = true } = props + + const formattedValue = value ? formatBalance(value, 6, needsParse, ticker) : '...' + + return ( + + h('.ether-balance.ether-balance-amount', { + style, + }, [ + h('div', { + style: { + display: 'inline', + width, + }, + }, this.renderBalance(formattedValue)), + ]) + + ) +} +EthBalanceComponent.prototype.renderBalance = function (value) { + if (value === 'None') return value + if (value === '...') return value + + const { + conversionRate, + shorten, + incoming, + currentCurrency, + hideTooltip, + styleOveride = {}, + showFiat = true, + } = this.props + const { fontSize, color, fontFamily, lineHeight } = styleOveride + + const { shortBalance, balance, label } = generateBalanceObject(value, shorten ? 1 : 3) + const balanceToRender = shorten ? shortBalance : balance + + const [ethNumber, ethSuffix] = value.split(' ') + const containerProps = hideTooltip ? {} : { + position: 'bottom', + title: `${ethNumber} ${ethSuffix}`, + } + + return ( + h(hideTooltip ? 'div' : Tooltip, + containerProps, + h('div.flex-column', [ + h('.flex-row', { + style: { + alignItems: 'flex-end', + lineHeight: lineHeight || '13px', + fontFamily: fontFamily || 'Montserrat Light', + textRendering: 'geometricPrecision', + }, + }, [ + h('div', { + style: { + width: '100%', + textAlign: 'right', + fontSize: fontSize || 'inherit', + color: color || 'inherit', + }, + }, incoming ? `+${balanceToRender}` : balanceToRender), + h('div', { + style: { + color: color || '#AEAEAE', + fontSize: fontSize || '12px', + marginLeft: '5px', + }, + }, label), + ]), + + showFiat ? h(FiatValue, { value: this.props.value, conversionRate, currentCurrency }) : null, + ]) + ) + ) +} diff --git a/ui/app/components/ui/eth-balance/eth-balance.component.js b/ui/app/components/ui/eth-balance/eth-balance.component.js deleted file mode 100644 index 925395602..000000000 --- a/ui/app/components/ui/eth-balance/eth-balance.component.js +++ /dev/null @@ -1,137 +0,0 @@ -import PropTypes from 'prop-types' -import React, {Component} from 'react' - -const { - formatBalance, - generateBalanceObject, -} = require('../../../helpers/utils/util') -const Tooltip = require('../tooltip.js') -const FiatValue = require('../fiat-value.js') - -export default class EthBalance extends Component { - static defaultProps = { - style: null, - styleOverride: {}, - showFiat: true, - needsParse: true, - width: undefined, - shorten: false, - incoming: false, - } - - static propTypes = { - conversionRate: PropTypes.any.isRequired, - shorten: PropTypes.bool, - incoming: PropTypes.bool, - currentCurrency: PropTypes.string.isRequired, - hideTooltip: PropTypes.bool, - styleOverride: PropTypes.object, - showFiat: PropTypes.bool, - ticker: PropTypes.string.isRequired, - value: PropTypes.string.isRequired, - style: PropTypes.object, - width: PropTypes.string, - needsParse: PropTypes.bool, - } - - renderBalance (value) { - if (value === 'None') { - return value - } - if (value === '...') { - return value - } - - const { - conversionRate, - shorten, - incoming, - currentCurrency, - hideTooltip, - styleOverride = {}, - showFiat = true, - } = this.props - const { fontSize, color, fontFamily, lineHeight } = styleOverride - - const { shortBalance, balance, label } = generateBalanceObject(value, shorten ? 1 : 3) - const balanceToRender = shorten ? shortBalance : balance - - const [ethNumber, ethSuffix] = value.split(' ') - const containerProps = hideTooltip ? {} : { - position: 'bottom', - title: `${ethNumber} ${ethSuffix}`, - } - - const TooltipComponent = hideTooltip ? 'div' : Tooltip - - return ( - -
    -
    -
    - { - incoming - ? `+${balanceToRender}` - : balanceToRender - } -
    -
    - {label} -
    -
    -
    - { - showFiat - ? ( - - ) - : null - } -
    - ) - } - - render () { - const { ticker, value, style, width, needsParse } = this.props - const formattedValue = value - ? formatBalance(value, 6, needsParse, ticker) - : '...' - - return ( -
    -
    - {this.renderBalance(formattedValue)} -
    -
    - ) - } -} diff --git a/ui/app/components/ui/eth-balance/eth-balance.container.js b/ui/app/components/ui/eth-balance/eth-balance.container.js deleted file mode 100644 index 8e36b6f78..000000000 --- a/ui/app/components/ui/eth-balance/eth-balance.container.js +++ /dev/null @@ -1,10 +0,0 @@ -import { connect } from 'react-redux' -import EthBalance from './eth-balance.component' - -function mapStateToProps (state) { - return { - ticker: state.metamask.ticker, - } -} - -export default connect(mapStateToProps)(EthBalance) diff --git a/ui/app/components/ui/eth-balance/index.js b/ui/app/components/ui/eth-balance/index.js deleted file mode 100644 index f40991784..000000000 --- a/ui/app/components/ui/eth-balance/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './eth-balance.container' diff --git a/ui/app/components/ui/export-text-container/export-text-container.component.js b/ui/app/components/ui/export-text-container/export-text-container.component.js index 23ae0b047..21fd5ecec 100644 --- a/ui/app/components/ui/export-text-container/export-text-container.component.js +++ b/ui/app/components/ui/export-text-container/export-text-container.component.js @@ -1,5 +1,6 @@ -import React, { Component } from 'react' +const { Component } = require('react') const PropTypes = require('prop-types') +const h = require('react-hyperscript') const copyToClipboard = require('copy-to-clipboard') const { exportAsFile } = require('../../../helpers/utils/util') @@ -9,33 +10,25 @@ class ExportTextContainer extends Component { const { t } = this.context return ( -
    -
    -
    - {text} -
    -
    -
    -
    copyToClipboard(text)} - > - -
    - {t('copyToClipboard')} -
    -
    -
    exportAsFile(filename, text)} - > - -
    - {t('saveAsCsvFile')} -
    -
    -
    -
    + h('.export-text-container', [ + h('.export-text-container__text-container', [ + h('.export-text-container__text.notranslate', text), + ]), + h('.export-text-container__buttons-container', [ + h('.export-text-container__button.export-text-container__button--copy', { + onClick: () => copyToClipboard(text), + }, [ + h('img', { src: 'images/copy-to-clipboard.svg' }), + h('.export-text-container__button-text', t('copyToClipboard')), + ]), + h('.export-text-container__button', { + onClick: () => exportAsFile(filename, text), + }, [ + h('img', { src: 'images/download.svg' }), + h('.export-text-container__button-text', t('saveAsCsvFile')), + ]), + ]), + ]) ) } } diff --git a/ui/app/components/ui/fiat-value.js b/ui/app/components/ui/fiat-value.js index a02264b01..02111ba49 100644 --- a/ui/app/components/ui/fiat-value.js +++ b/ui/app/components/ui/fiat-value.js @@ -1,4 +1,5 @@ -import React, { Component } from 'react' +const Component = require('react').Component +const h = require('react-hyperscript') const inherits = require('util').inherits const formatBalance = require('../../helpers/utils/util').formatBalance @@ -16,11 +17,9 @@ FiatValue.prototype.render = function () { const value = formatBalance(props.value, 6) - if (value === 'None') { - return value - } - let fiatDisplayNumber, fiatTooltipNumber - const splitBalance = value.split(' ') + if (value === 'None') return value + var fiatDisplayNumber, fiatTooltipNumber + var splitBalance = value.split(' ') if (conversionRate !== 0) { fiatTooltipNumber = Number(splitBalance[0]) * conversionRate @@ -37,38 +36,31 @@ function fiatDisplay (fiatDisplayNumber, fiatSuffix, styleOveride = {}) { const { fontSize, color, fontFamily, lineHeight } = styleOveride if (fiatDisplayNumber !== 'N/A') { - return ( -
    -
    - {fiatDisplayNumber} -
    -
    - {fiatSuffix} -
    -
    - ) + return h('.flex-row', { + style: { + alignItems: 'flex-end', + lineHeight: lineHeight || '13px', + fontFamily: fontFamily || 'Montserrat Light', + textRendering: 'geometricPrecision', + }, + }, [ + h('div', { + style: { + width: '100%', + textAlign: 'right', + fontSize: fontSize || '12px', + color: color || '#333333', + }, + }, fiatDisplayNumber), + h('div', { + style: { + color: color || '#AEAEAE', + marginLeft: '5px', + fontSize: fontSize || '12px', + }, + }, fiatSuffix), + ]) } else { - return
    + return h('div') } } diff --git a/ui/app/components/ui/hex-to-decimal/tests/hex-to-decimal.component.test.js b/ui/app/components/ui/hex-to-decimal/tests/hex-to-decimal.component.test.js index 848ef2c4f..c98da9ad4 100644 --- a/ui/app/components/ui/hex-to-decimal/tests/hex-to-decimal.component.test.js +++ b/ui/app/components/ui/hex-to-decimal/tests/hex-to-decimal.component.test.js @@ -5,24 +5,20 @@ import HexToDecimal from '../hex-to-decimal.component' describe('HexToDecimal Component', () => { it('should render a prefixed hex as a decimal with a className', () => { - const wrapper = shallow(( - - )) + const wrapper = shallow() assert.ok(wrapper.hasClass('hex-to-decimal')) assert.equal(wrapper.text(), '12345') }) it('should render an unprefixed hex as a decimal with a className', () => { - const wrapper = shallow(( - - )) + const wrapper = shallow() assert.ok(wrapper.hasClass('hex-to-decimal')) assert.equal(wrapper.text(), '6789') diff --git a/ui/app/components/ui/icon-with-fallback/icon-with-fallback.component.js b/ui/app/components/ui/icon-with-fallback/icon-with-fallback.component.js deleted file mode 100644 index 13b3e93d7..000000000 --- a/ui/app/components/ui/icon-with-fallback/icon-with-fallback.component.js +++ /dev/null @@ -1,42 +0,0 @@ -import React, { PureComponent } from 'react' -import PropTypes from 'prop-types' - -export default class IconWithFallback extends PureComponent { - static propTypes = { - icon: PropTypes.string, - name: PropTypes.string, - } - - static defaultProps = { - name: '', - icon: null, - } - - state = { - iconError: false, - } - - render () { - const { icon, name } = this.props - - return ( -
    -
    - { !this.state.iconError && icon - ? ( - this.setState({ iconError: true })} - /> - ) - : ( - - { name.length ? name.charAt(0).toUpperCase() : '' } - - ) - } -
    - ) - } -} diff --git a/ui/app/components/ui/icon-with-fallback/index.js b/ui/app/components/ui/icon-with-fallback/index.js deleted file mode 100644 index 8c1f9a154..000000000 --- a/ui/app/components/ui/icon-with-fallback/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './icon-with-fallback.component' diff --git a/ui/app/components/ui/icon-with-fallback/index.scss b/ui/app/components/ui/icon-with-fallback/index.scss deleted file mode 100644 index 02ffe371d..000000000 --- a/ui/app/components/ui/icon-with-fallback/index.scss +++ /dev/null @@ -1,30 +0,0 @@ -.icon-with-fallback { - &__identicon-container { - position: relative; - display: flex; - justify-content: center; - align-items: center; - height: 32px; - width: 32px; - } - - &__identicon-border { - height: 32px; - width: 32px; - border-radius: 50%; - border: 1px solid #F2F3F4; - position: absolute; - background: #FFFFFF; - } - - &__identicon { - width: 24px; - height: 24px; - z-index: 1; - - &--default { - z-index: 1; - color: black; - } - } -} diff --git a/ui/app/components/ui/identicon/tests/identicon.component.test.js b/ui/app/components/ui/identicon/tests/identicon.component.test.js index b1efb158c..2944818f5 100644 --- a/ui/app/components/ui/identicon/tests/identicon.component.test.js +++ b/ui/app/components/ui/identicon/tests/identicon.component.test.js @@ -18,7 +18,7 @@ describe('Identicon', () => { it('renders default eth_logo identicon with no props', () => { const wrapper = mount( - + ) assert.equal(wrapper.find('img.balance-icon').prop('src'), './images/eth_logo.svg') diff --git a/ui/app/components/ui/loading-screen/loading-screen.component.js b/ui/app/components/ui/loading-screen/loading-screen.component.js index b3fb3308d..6b843cfee 100644 --- a/ui/app/components/ui/loading-screen/loading-screen.component.js +++ b/ui/app/components/ui/loading-screen/loading-screen.component.js @@ -1,33 +1,31 @@ -import React, {Component} from 'react' +const { Component } = require('react') +const h = require('react-hyperscript') const PropTypes = require('prop-types') const Spinner = require('../spinner') class LoadingScreen extends Component { - static defaultProps = { - loadingMessage: null, - } - - static propTypes = { - loadingMessage: PropTypes.string, - } - renderMessage () { const { loadingMessage } = this.props - return loadingMessage - ? {loadingMessage} - : null + return loadingMessage && h('span', loadingMessage) } render () { return ( -
    -
    - - {this.renderMessage()} -
    -
    + h('.loading-overlay', [ + h('.loading-overlay__container', [ + h(Spinner, { + color: '#F7C06C', + }), + + this.renderMessage(), + ]), + ]) ) } } +LoadingScreen.propTypes = { + loadingMessage: PropTypes.string, +} + module.exports = LoadingScreen diff --git a/ui/app/components/ui/mascot.js b/ui/app/components/ui/mascot.js index f84961745..3b0d3e31b 100644 --- a/ui/app/components/ui/mascot.js +++ b/ui/app/components/ui/mascot.js @@ -1,5 +1,6 @@ -import React, { Component } from 'react' const inherits = require('util').inherits +const Component = require('react').Component +const h = require('react-hyperscript') const metamaskLogo = require('metamask-logo') const debounce = require('debounce') @@ -19,22 +20,20 @@ function Mascot ({width = '200', height = '200'}) { this.unfollowMouse = this.logo.setFollowMouse.bind(this.logo, false) } -Mascot.prototype.render = function Mascot () { +Mascot.prototype.render = function () { // this is a bit hacky // the event emitter is on `this.props` // and we dont get that until render this.handleAnimationEvents() - return ( -
    - ) + + return h('#metamask-mascot-container', { + style: { zIndex: 0 }, + }) } Mascot.prototype.componentDidMount = function () { - const targetDivId = 'metamask-mascot-container' - const container = document.getElementById(targetDivId) + var targetDivId = 'metamask-mascot-container' + var container = document.getElementById(targetDivId) container.appendChild(this.logo.container) } @@ -47,9 +46,7 @@ Mascot.prototype.componentWillUnmount = function () { Mascot.prototype.handleAnimationEvents = function () { // only setup listeners once - if (this.animations) { - return - } + if (this.animations) return this.animations = this.props.animationEventEmitter this.animations.on('point', this.lookAt.bind(this)) this.animations.on('setFollowMouse', this.logo.setFollowMouse.bind(this.logo)) 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 338df83d8..a2cf0100b 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 @@ -14,7 +14,6 @@ export default class PageContainerFooter extends Component { disabled: PropTypes.bool, submitButtonType: PropTypes.string, hideCancel: PropTypes.bool, - buttonSizeLarge: PropTypes.bool, } static contextTypes = { @@ -32,27 +31,24 @@ export default class PageContainerFooter extends Component { submitButtonType, hideCancel, cancelButtonType, - buttonSizeLarge = false, } = this.props return (
    - {!hideCancel && ( - - )} + {!hideCancel && } - -
    - ) + return h('div.hw-list-pagination', [ + h( + 'button.hw-list-pagination__button', + { + onClick: this.goToPreviousPage, + }, + `< ${this.context.t('prev')}` + ), + + h( + 'button.hw-list-pagination__button', + { + onClick: this.goToNextPage, + }, + `${this.context.t('next')} >` + ), + ]) } renderButtons () { @@ -133,49 +139,40 @@ class AccountList extends Component { buttonProps.disabled = true } - return ( -
    - - -
    - ) + return h('div.new-account-connect-form__buttons', {}, [ + h(Button, { + type: 'default', + large: true, + className: 'new-account-connect-form__button', + onClick: this.props.onCancel.bind(this), + }, [this.context.t('cancel')]), + + h(Button, { + type: 'primary', + large: true, + className: 'new-account-connect-form__button unlock', + disabled, + onClick: this.props.onUnlockAccount.bind(this, this.props.device), + }, [this.context.t('unlock')]), + ]) } renderForgetDevice () { - return ( -
    - ) + return h('div.hw-forget-device-container', {}, [ + h('a', { + onClick: this.props.onForgetDevice.bind(this, this.props.device), + }, this.context.t('forgetDevice')), + ]) } render () { - return ( -
    - {this.renderHeader()} - {this.renderAccounts()} - {this.renderPagination()} - {this.renderButtons()} - {this.renderForgetDevice()} -
    - ) + return h('div.new-account-connect-form.account-list', {}, [ + this.renderHeader(), + this.renderAccounts(), + this.renderPagination(), + this.renderButtons(), + this.renderForgetDevice(), + ]) } } 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 6be85f5b7..3b45e7293 100644 --- a/ui/app/pages/create-account/connect-hardware/connect-screen.js +++ b/ui/app/pages/create-account/connect-hardware/connect-screen.js @@ -1,228 +1,202 @@ -import classnames from 'classnames' -import PropTypes from 'prop-types' -import React, { Component } from 'react' +const { Component } = require('react') +const PropTypes = require('prop-types') +const h = require('react-hyperscript') import Button from '../../../components/ui/button' class ConnectScreen extends Component { - static contextTypes = { - t: PropTypes.func, - } - - static propTypes = { - connectToHardwareWallet: PropTypes.func.isRequired, - browserSupported: PropTypes.bool.isRequired, - } - - state = { - selectedDevice: null, - } - - connect = () => { - if (this.state.selectedDevice) { - this.props.connectToHardwareWallet(this.state.selectedDevice) - } - return null - } - - renderConnectToTrezorButton () { - return ( - - ) - } - - renderConnectToLedgerButton () { - return ( - - ) - } - - renderButtons () { - return ( -
    -
    - {this.renderConnectToLedgerButton()} - {this.renderConnectToTrezorButton()} -
    - -
    - ) - } - - renderUnsupportedBrowser () { - return ( -
    -
    -

    {this.context.t('browserNotSupported')}

    -

    {this.context.t('chromeRequiredForHardwareWallets')}

    -
    - -
    - ) - } - - renderHeader () { - return ( -
    -

    {this.context.t('hardwareWallets')}

    -

    {this.context.t('hardwareWalletsMsg')}

    -
    - ) - } - - getAffiliateLinks () { - const links = { - trezor: `Trezor`, - ledger: `Ledger`, - } - - const text = this.context.t('orderOneHere') - const response = text.replace('Trezor', links.trezor).replace('Ledger', links.ledger) - - return ( -
    - ) - } - - renderTrezorAffiliateLink () { - return ( -
    -

    {this.context.t('dontHaveAHardwareWallet')}

    - {this.getAffiliateLinks()} -
    - ) - } - - - scrollToTutorial = () => { - if (this.referenceNode) { - this.referenceNode.scrollIntoView({behavior: 'smooth'}) + constructor (props) { + super(props) + this.state = { + selectedDevice: null, } } - renderLearnMore () { - return ( -

    - {this.context.t('learnMore')} - -

    - ) - } + connect = () => { + if (this.state.selectedDevice) { + this.props.connectToHardwareWallet(this.state.selectedDevice) + } + return null + } - renderTutorialSteps () { - const steps = [ - { - asset: 'hardware-wallet-step-1', - dimensions: {width: '225px', height: '75px'}, - title: this.context.t('step1HardwareWallet'), - message: this.context.t('step1HardwareWalletMsg'), + renderConnectToTrezorButton () { + return h( + `button.hw-connect__btn${this.state.selectedDevice === 'trezor' ? '.selected' : ''}`, + { onClick: _ => this.setState({selectedDevice: 'trezor'}) }, + h('img.hw-connect__btn__img', { + src: 'images/trezor-logo.svg', + }) + ) + } + + renderConnectToLedgerButton () { + return h( + `button.hw-connect__btn${this.state.selectedDevice === 'ledger' ? '.selected' : ''}`, + { onClick: _ => this.setState({selectedDevice: 'ledger'}) }, + h('img.hw-connect__btn__img', { + src: 'images/ledger-logo.svg', + }) + ) + } + + renderButtons () { + return ( + h('div', {}, [ + h('div.hw-connect__btn-wrapper', {}, [ + this.renderConnectToLedgerButton(), + this.renderConnectToTrezorButton(), + ]), + h(Button, { + type: 'primary', + large: true, + className: 'hw-connect__connect-btn', + onClick: this.connect, + disabled: !this.state.selectedDevice, + }, this.context.t('connect')), + ]) + ) + } + + renderUnsupportedBrowser () { + return ( + h('div.new-account-connect-form.unsupported-browser', {}, [ + h('div.hw-connect', [ + h('h3.hw-connect__title', {}, this.context.t('browserNotSupported')), + h('p.hw-connect__msg', {}, this.context.t('chromeRequiredForHardwareWallets')), + ]), + h(Button, { + type: 'primary', + large: true, + onClick: () => global.platform.openWindow({ + url: 'https://google.com/chrome', + }), + }, this.context.t('downloadGoogleChrome')), + ]) + ) + } + + renderHeader () { + return ( + h('div.hw-connect__header', {}, [ + h('h3.hw-connect__header__title', {}, this.context.t('hardwareWallets')), + h('p.hw-connect__header__msg', {}, this.context.t('hardwareWalletsMsg')), + ]) + ) + } + + getAffiliateLinks () { + const links = { + trezor: `Trezor`, + ledger: `Ledger`, + } + + const text = this.context.t('orderOneHere') + const response = text.replace('Trezor', links.trezor).replace('Ledger', links.ledger) + + return h('div.hw-connect__get-hw__msg', { dangerouslySetInnerHTML: {__html: response }}) + } + + renderTrezorAffiliateLink () { + return h('div.hw-connect__get-hw', {}, [ + h('p.hw-connect__get-hw__msg', {}, this.context.t('dontHaveAHardwareWallet')), + this.getAffiliateLinks(), + ]) + } + + + scrollToTutorial = () => { + if (this.referenceNode) this.referenceNode.scrollIntoView({behavior: 'smooth'}) + } + + renderLearnMore () { + return ( + h('p.hw-connect__learn-more', { + onClick: this.scrollToTutorial, + }, [ + this.context.t('learnMore'), + h('img.hw-connect__learn-more__arrow', { src: 'images/caret-right.svg'}), + ]) + ) + } + + renderTutorialSteps () { + const steps = [ + { + asset: 'hardware-wallet-step-1', + dimensions: {width: '225px', height: '75px'}, + title: this.context.t('step1HardwareWallet'), + message: this.context.t('step1HardwareWalletMsg'), + }, + { + asset: 'hardware-wallet-step-2', + dimensions: {width: '300px', height: '100px'}, + title: this.context.t('step2HardwareWallet'), + message: this.context.t('step2HardwareWalletMsg'), + }, + { + asset: 'hardware-wallet-step-3', + dimensions: {width: '120px', height: '90px'}, + title: this.context.t('step3HardwareWallet'), + message: this.context.t('step3HardwareWalletMsg'), + }, + ] + + return h('.hw-tutorial', { + ref: node => { this.referenceNode = node }, }, - { - asset: 'hardware-wallet-step-2', - dimensions: {width: '300px', height: '100px'}, - title: this.context.t('step2HardwareWallet'), - message: this.context.t('step2HardwareWalletMsg'), - }, - { - asset: 'hardware-wallet-step-3', - dimensions: {width: '120px', height: '90px'}, - title: this.context.t('step3HardwareWallet'), - message: this.context.t('step3HardwareWalletMsg'), - }, - ] - - return ( -
    { - this.referenceNode = node - }} - > - {steps.map((step, index) => ( -
    -

    {step.title}

    -

    {step.message}

    - -
    - ))} -
    - ) - } - - renderFooter () { - return ( -
    -

    {this.context.t('readyToConnect')}

    - {this.renderButtons()} -

    - {this.context.t('havingTroubleConnecting')} - - {this.context.t('getHelp')} - -

    -
    - ) - } - - renderConnectScreen () { - return ( -
    - {this.renderHeader()} - {this.renderButtons()} - {this.renderTrezorAffiliateLink()} - {this.renderLearnMore()} - {this.renderTutorialSteps()} - {this.renderFooter()} -
    - ) - } - - render () { - if (this.props.browserSupported) { - return this.renderConnectScreen() + steps.map((step) => ( + h('div.hw-connect', {}, [ + h('h3.hw-connect__title', {}, step.title), + h('p.hw-connect__msg', {}, step.message), + h('img.hw-connect__step-asset', { src: `images/${step.asset}.svg`, ...step.dimensions }), + ]) + )) + ) } - return this.renderUnsupportedBrowser() - } + + renderFooter () { + return ( + h('div.hw-connect__footer', {}, [ + h('h3.hw-connect__footer__title', {}, this.context.t('readyToConnect')), + this.renderButtons(), + h('p.hw-connect__footer__msg', {}, [ + this.context.t('havingTroubleConnecting'), + h('a.hw-connect__footer__link', { + href: 'https://support.metamask.io/', + target: '_blank', + }, this.context.t('getHelp')), + ]), + ]) + ) + } + + renderConnectScreen () { + return ( + h('div.new-account-connect-form', {}, [ + this.renderHeader(), + this.renderButtons(), + this.renderTrezorAffiliateLink(), + this.renderLearnMore(), + this.renderTutorialSteps(), + this.renderFooter(), + ]) + ) + } + + render () { + if (this.props.browserSupported) { + return this.renderConnectScreen() + } + return this.renderUnsupportedBrowser() + } +} + +ConnectScreen.propTypes = { + connectToHardwareWallet: PropTypes.func.isRequired, + browserSupported: PropTypes.bool.isRequired, +} + +ConnectScreen.contextTypes = { + t: PropTypes.func, } module.exports = ConnectScreen diff --git a/ui/app/pages/create-account/connect-hardware/index.js b/ui/app/pages/create-account/connect-hardware/index.js index 351699e8a..66851c780 100644 --- a/ui/app/pages/create-account/connect-hardware/index.js +++ b/ui/app/pages/create-account/connect-hardware/index.js @@ -1,5 +1,6 @@ -import React, { Component } from 'react' +const { Component } = require('react') const PropTypes = require('prop-types') +const h = require('react-hyperscript') const connect = require('react-redux').connect const actions = require('../../../store/actions') const { getMetaMaskAccounts } = require('../../../selectors/selectors') @@ -115,11 +116,10 @@ class ConnectHardwareForm extends Component { } }) .catch(e => { - const errorMessage = e.message - if (errorMessage === 'Window blocked') { + if (e === 'Window blocked') { this.setState({ browserSupported: false, error: null}) - } else if (errorMessage !== 'Window closed' && errorMessage !== 'Popup closed') { - this.setState({ error: errorMessage }) + } else if (e !== 'Window closed' && e !== 'Popup closed') { + this.setState({ error: e.toString() }) } }) } @@ -134,7 +134,7 @@ class ConnectHardwareForm extends Component { unlocked: false, }) }).catch(e => { - this.setState({ error: e.message }) + this.setState({ error: e.toString() }) }) } @@ -162,10 +162,10 @@ class ConnectHardwareForm extends Component { name: 'Error connecting hardware wallet', }, customVariables: { - error: e.message, + error: e.toString(), }, }) - this.setState({ error: e.message }) + this.setState({ error: e.toString() }) }) } @@ -175,53 +175,40 @@ class ConnectHardwareForm extends Component { renderError () { return this.state.error - ? ( - - {this.state.error} - - ) + ? h('span.error', { style: { margin: '20px 20px 10px', display: 'block', textAlign: 'center' } }, this.state.error) : null } renderContent () { if (!this.state.accounts.length) { - return ( - - ) + return h(ConnectScreen, { + connectToHardwareWallet: this.connectToHardwareWallet, + browserSupported: this.state.browserSupported, + }) } - return ( - - ) + return h(AccountList, { + onPathChange: this.onPathChange, + selectedPath: this.props.defaultHdPaths[this.state.device], + device: this.state.device, + accounts: this.state.accounts, + selectedAccount: this.state.selectedAccount, + onAccountChange: this.onAccountChange, + network: this.props.network, + getPage: this.getPage, + history: this.props.history, + onUnlockAccount: this.onUnlockAccount, + onForgetDevice: this.onForgetDevice, + onCancel: this.onCancel, + onAccountRestriction: this.onAccountRestriction, + }) } render () { - return ( -
    - {this.renderError()} - {this.renderContent()} -
    - ) + return h('div', [ + this.renderError(), + this.renderContent(), + ]) } } diff --git a/ui/app/pages/create-account/create-account.component.js b/ui/app/pages/create-account/create-account.component.js index 1f8bc0cc5..aa05af975 100644 --- a/ui/app/pages/create-account/create-account.component.js +++ b/ui/app/pages/create-account/create-account.component.js @@ -23,15 +23,15 @@ export default class CreateAccountPage extends Component { return (
    -
    history.push(NEW_ACCOUNT_ROUTE)}> - {this.context.t('create')} -
    -
    history.push(IMPORT_ACCOUNT_ROUTE)}> - {this.context.t('import')} -
    -
    history.push(CONNECT_HARDWARE_ROUTE)}> - {this.context.t('connect')} -
    +
    history.push(NEW_ACCOUNT_ROUTE)}>{ + this.context.t('create') + }
    +
    history.push(IMPORT_ACCOUNT_ROUTE)}>{ + this.context.t('import') + }
    +
    history.push(CONNECT_HARDWARE_ROUTE)}>{ + this.context.t('connect') + }
    ) } diff --git a/ui/app/pages/create-account/import-account/index.js b/ui/app/pages/create-account/import-account/index.js index 5ef837715..48d8f8838 100644 --- a/ui/app/pages/create-account/import-account/index.js +++ b/ui/app/pages/create-account/import-account/index.js @@ -1,5 +1,6 @@ -import React, { Component } from 'react' const inherits = require('util').inherits +const Component = require('react').Component +const h = require('react-hyperscript') const PropTypes = require('prop-types') const connect = require('react-redux').connect import Select from 'react-select' @@ -34,45 +35,47 @@ AccountImportSubview.prototype.render = function () { const { type } = state return ( -
    -
    - {this.context.t('importAccountMsg')} - { + }, + onClick: () => { global.platform.openWindow({ url: 'https://metamask.zendesk.com/hc/en-us/articles/360015289932', }) - }} - > - {this.context.t('here')} - -
    -
    -
    - {this.context.t('selectType')} -
    - -
    - - -
    - { - error - ? {error} - : null - } -
    + }, + }), + + h('input.new-account-import-form__input-password', { + type: 'password', + placeholder: this.context.t('enterPassword'), + id: 'json-password-box', + onKeyPress: this.createKeyringOnEnter.bind(this), + }), + + h('div.new-account-create-form__buttons', {}, [ + + h(Button, { + type: 'default', + large: true, + className: 'new-account-create-form__button', + onClick: () => this.props.history.push(DEFAULT_ROUTE), + }, [this.context.t('cancel')]), + + h(Button, { + type: 'secondary', + large: true, + className: 'new-account-create-form__button', + onClick: () => this.createNewKeychain(), + }, [this.context.t('import')]), + + ]), + + error ? h('span.error', error) : null, + ]) ) } - onLoad (event) { - this.setState({ - fileContents: event.target.result, - }) + onLoad (event, file) { + this.setState({file: file, fileContents: event.target.result}) } createKeyringOnEnter (event) { @@ -83,7 +89,14 @@ class JsonImportSubview extends Component { createNewKeychain () { const { firstAddress, displayWarning, importNewJsonAccount, setSelectedAddress, history } = this.props - const { fileContents } = this.state + const state = this.state + + if (!state) { + const message = this.context.t('validFileImport') + return displayWarning(message) + } + + const { fileContents } = state if (!fileContents) { const message = this.context.t('needImportFile') 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 bbb2b70bd..0cdf25ce9 100644 --- a/ui/app/pages/create-account/import-account/private-key.js +++ b/ui/app/pages/create-account/import-account/private-key.js @@ -1,5 +1,6 @@ -import React, { Component } from 'react' const inherits = require('util').inherits +const Component = require('react').Component +const h = require('react-hyperscript') const { withRouter } = require('react-router-dom') const { compose } = require('recompose') const PropTypes = require('prop-types') @@ -43,49 +44,47 @@ function PrivateKeyImportView () { Component.call(this) } -PrivateKeyImportView.prototype.render = function PrivateKeyImportView () { +PrivateKeyImportView.prototype.render = function () { const { error, displayWarning } = this.props return ( -
    - - {this.context.t('pastePrivateKey')} - -
    - this.createKeyringOnEnter(e)} - /> -
    -
    - - -
    - { - error - ? {error} - : null - } -
    + }, + }, [this.context.t('cancel')]), + + h(Button, { + type: 'secondary', + large: true, + className: 'new-account-create-form__button', + onClick: () => this.createNewKeychain(), + }, [this.context.t('import')]), + + ]), + + error ? h('span.error', error) : null, + ]) ) } diff --git a/ui/app/pages/create-account/import-account/seed.js b/ui/app/pages/create-account/import-account/seed.js new file mode 100644 index 000000000..73332f926 --- /dev/null +++ b/ui/app/pages/create-account/import-account/seed.js @@ -0,0 +1,35 @@ +const inherits = require('util').inherits +const Component = require('react').Component +const h = require('react-hyperscript') +const PropTypes = require('prop-types') +const connect = require('react-redux').connect + +SeedImportSubview.contextTypes = { + t: PropTypes.func, +} + +module.exports = connect(mapStateToProps)(SeedImportSubview) + + +function mapStateToProps () { + return {} +} + +inherits(SeedImportSubview, Component) +function SeedImportSubview () { + Component.call(this) +} + +SeedImportSubview.prototype.render = function () { + return ( + h('div', { + style: { + }, + }, [ + this.context.t('pasteSeed'), + h('textarea'), + h('br'), + h('button', this.context.t('submit')), + ]) + ) +} diff --git a/ui/app/pages/create-account/new-account.component.js b/ui/app/pages/create-account/new-account.component.js index fbb5e128f..6dc6419b5 100644 --- a/ui/app/pages/create-account/new-account.component.js +++ b/ui/app/pages/create-account/new-account.component.js @@ -49,8 +49,7 @@ export default class NewAccountCreateForm extends Component { {this.context.t('accountName')}
    - this.setState({ newAccountName: event.target.value })} @@ -62,17 +61,13 @@ export default class NewAccountCreateForm extends Component { large className="new-account-create-form__button" onClick={() => history.push(DEFAULT_ROUTE)} - > - {this.context.t('cancel')} - + >{this.context.t('cancel')} + >{this.context.t('create')}
    ) 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 53d4614ad..e1c0b21ed 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 @@ -175,12 +175,6 @@ export default class ImportWithSeedPhrase extends PureComponent { return !passwordError && !confirmPasswordError && !seedPhraseError } - onTermsKeyPress = ({key}) => { - if (key === ' ' || key === 'Enter') { - this.toggleTermsCheck() - } - } - toggleTermsCheck = () => { this.context.metricsEvent({ eventOpts: { @@ -189,6 +183,7 @@ export default class ImportWithSeedPhrase extends PureComponent { name: 'Check ToS', }, }) + this.setState((prevState) => ({ termsChecked: !prevState.termsChecked, })) @@ -272,19 +267,11 @@ export default class ImportWithSeedPhrase extends PureComponent { largeLabel />
    -
    +
    {termsChecked ? : null}
    - - I have read and agree to the  - + I have read and agree to the { + const { history } = this.props + + event.preventDefault() + history.push(INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE) + } + toggleTermsCheck = () => { this.context.metricsEvent({ eventOpts: { @@ -128,12 +136,6 @@ export default class NewAccount extends PureComponent { })) } - onTermsKeyPress = ({key}) => { - if (key === ' ' || key === 'Enter') { - this.toggleTermsCheck() - } - } - render () { const { t } = this.context const { password, confirmPassword, passwordError, confirmPasswordError, termsChecked } = this.state @@ -193,19 +195,11 @@ export default class NewAccount extends PureComponent { largeLabel />
    -
    +
    {termsChecked ? : null}
    - - I have read and agree to the  -
    + I have read and agree to the { - const { history, completeOnboarding, completionMetaMetricsName, onboardingInitiator } = this.props - - await completeOnboarding() - this.context.metricsEvent({ - eventOpts: { - category: 'Onboarding', - action: 'Onboarding Complete', - name: completionMetaMetricsName, - }, - }) - - if (onboardingInitiator) { - await returnToOnboardingInitiator(onboardingInitiator) - } - history.push(DEFAULT_ROUTE) } render () { const { t } = this.context - const { onboardingInitiator } = this.props + const { history, completeOnboarding, completionMetaMetricsName } = this.props return (
    @@ -73,8 +49,7 @@ export default class EndOfFlowScreen extends PureComponent { { '• ' + t('endOfFlowMessage7') }
    ) } diff --git a/ui/app/pages/first-time-flow/end-of-flow/end-of-flow.container.js b/ui/app/pages/first-time-flow/end-of-flow/end-of-flow.container.js index 2bb9ef15a..38313806c 100644 --- a/ui/app/pages/first-time-flow/end-of-flow/end-of-flow.container.js +++ b/ui/app/pages/first-time-flow/end-of-flow/end-of-flow.container.js @@ -1,22 +1,21 @@ import { connect } from 'react-redux' import EndOfFlow from './end-of-flow.component' import { setCompletedOnboarding } from '../../../store/actions' -import { getOnboardingInitiator } from '../first-time-flow.selectors' const firstTimeFlowTypeNameMap = { create: 'New Wallet Created', 'import': 'New Wallet Imported', } -const mapStateToProps = (state) => { - const { metamask: { firstTimeFlowType } } = state +const mapStateToProps = ({ metamask }) => { + const { firstTimeFlowType } = metamask return { completionMetaMetricsName: firstTimeFlowTypeNameMap[firstTimeFlowType], - onboardingInitiator: getOnboardingInitiator(state), } } + const mapDispatchToProps = dispatch => { return { completeOnboarding: () => dispatch(setCompletedOnboarding()), diff --git a/ui/app/pages/first-time-flow/end-of-flow/index.scss b/ui/app/pages/first-time-flow/end-of-flow/index.scss index de603fce4..d7eb4513b 100644 --- a/ui/app/pages/first-time-flow/end-of-flow/index.scss +++ b/ui/app/pages/first-time-flow/end-of-flow/index.scss @@ -50,4 +50,4 @@ font-size: 80px; margin-top: 70px; } -} +} \ No newline at end of file diff --git a/ui/app/pages/first-time-flow/first-time-flow.selectors.js b/ui/app/pages/first-time-flow/first-time-flow.selectors.js index 74cad5e12..e6cd5a84a 100644 --- a/ui/app/pages/first-time-flow/first-time-flow.selectors.js +++ b/ui/app/pages/first-time-flow/first-time-flow.selectors.js @@ -4,6 +4,12 @@ import { DEFAULT_ROUTE, } from '../../helpers/constants/routes' +const selectors = { + getFirstTimeFlowTypeRoute, +} + +module.exports = selectors + function getFirstTimeFlowTypeRoute (state) { const { firstTimeFlowType } = state.metamask @@ -18,25 +24,3 @@ function getFirstTimeFlowTypeRoute (state) { return nextRoute } - -const getOnboardingInitiator = (state) => { - const { onboardingTabs } = state.metamask - - if (!onboardingTabs || Object.keys(onboardingTabs).length !== 1) { - return null - } - - const location = Object.keys(onboardingTabs)[0] - const tabId = onboardingTabs[location] - return { - location, - tabId, - } -} - -const selectors = { - getFirstTimeFlowTypeRoute, - getOnboardingInitiator, -} - -module.exports = selectors 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 14c85b923..ad1ffbf42 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 @@ -142,8 +142,7 @@ export default class MetaMetricsOptIn extends Component { disabled={false} />
    - This data is aggregated and is therefore anonymous for the purposes of General Data Protection Regulation (EU) 2016/679. For more information in relation to our privacy practices, please see our  - { - const tab = await (new Promise((resolve) => { - extension.tabs.update(onboardingInitiator.tabId, { active: true }, (tab) => { - if (tab) { - resolve(tab) - } else { - // silence console message about unchecked error - if (extension.runtime.lastError) { - log.debug(extension.runtime.lastError) - } - resolve() - } - }) - })) - - if (!tab) { - // this case can happen if the tab was closed since being checked with `extension.tabs.get` - log.warn(`Setting current tab to onboarding initator has failed; falling back to redirect`) - window.location.assign(onboardingInitiator.location) - } else { - window.close() - } -} - -export const returnToOnboardingInitiator = async (onboardingInitiator) => { - const tab = await (new Promise((resolve) => { - extension.tabs.get(onboardingInitiator.tabId, (tab) => { - if (tab) { - resolve(tab) - } else { - // silence console message about unchecked error - if (extension.runtime.lastError) { - log.debug(extension.runtime.lastError) - } - resolve() - } - }) - })) - - if (tab) { - await returnToOnboardingInitiatorTab(onboardingInitiator) - } else { - window.location.assign(onboardingInitiator.location) - } -} 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 index e9bef631e..7e0652f8f 100644 --- 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 @@ -13,7 +13,7 @@ class DraggableSeed extends Component { isOver: PropTypes.bool, canDrop: PropTypes.bool, // Own Props - onClick: PropTypes.func, + onClick: PropTypes.func.isRequired, setHoveringIndex: PropTypes.func.isRequired, index: PropTypes.number, draggingSeedIndex: PropTypes.number, @@ -25,7 +25,6 @@ class DraggableSeed extends Component { static defaultProps = { className: '', - onClick: undefined, } componentWillReceiveProps (nextProps) { diff --git a/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.scss b/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.scss index 583d92a0b..dfe9868cf 100644 --- a/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.scss +++ b/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.scss @@ -60,7 +60,7 @@ } button { - margin-top: 0px; + margin-top: 0xp; } &__buttons { 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 922685059..4144fb878 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 @@ -3,10 +3,8 @@ import PropTypes from 'prop-types' import classnames from 'classnames' import LockIcon from '../../../../components/ui/lock-icon' import Button from '../../../../components/ui/button' -import Snackbar from '../../../../components/ui/snackbar' import { INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE, DEFAULT_ROUTE } from '../../../../helpers/constants/routes' import { exportAsFile } from '../../../../helpers/utils/util' -import { returnToOnboardingInitiator } from '../../onboarding-initiator-util' export default class RevealSeedPhrase extends PureComponent { static contextTypes = { @@ -19,10 +17,6 @@ export default class RevealSeedPhrase extends PureComponent { seedPhrase: PropTypes.string, setSeedPhraseBackedUp: PropTypes.func, setCompletedOnboarding: PropTypes.func, - onboardingInitiator: PropTypes.exact({ - location: PropTypes.string, - tabId: PropTypes.number, - }), } state = { @@ -33,7 +27,8 @@ export default class RevealSeedPhrase extends PureComponent { exportAsFile('MetaMask Secret Backup Phrase', this.props.seedPhrase, 'text/plain') } - handleNext = () => { + handleNext = event => { + event.preventDefault() const { isShowingSeedPhrase } = this.state const { history } = this.props @@ -52,8 +47,9 @@ export default class RevealSeedPhrase extends PureComponent { history.push(INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE) } - handleSkip = async () => { - const { history, setSeedPhraseBackedUp, setCompletedOnboarding, onboardingInitiator } = this.props + handleSkip = event => { + event.preventDefault() + const { history, setSeedPhraseBackedUp, setCompletedOnboarding } = this.props this.context.metricsEvent({ eventOpts: { @@ -63,12 +59,10 @@ export default class RevealSeedPhrase extends PureComponent { }, }) - await Promise.all([setCompletedOnboarding(), setSeedPhraseBackedUp(false)]) - - if (onboardingInitiator) { - await returnToOnboardingInitiator(onboardingInitiator) - } - history.push(DEFAULT_ROUTE) + Promise.all([setCompletedOnboarding(), setSeedPhraseBackedUp(false)]) + .then(() => { + history.push(DEFAULT_ROUTE) + }) } renderSecretWordsContainer () { @@ -78,12 +72,10 @@ export default class RevealSeedPhrase extends PureComponent { return (
    - ) } diff --git a/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.container.js b/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.container.js index 11a26fb6d..7ada36afc 100644 --- a/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.container.js +++ b/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.container.js @@ -4,13 +4,6 @@ import { setCompletedOnboarding, setSeedPhraseBackedUp, } from '../../../../store/actions' -import { getOnboardingInitiator } from '../../first-time-flow.selectors' - -const mapStateToProps = (state) => { - return { - onboardingInitiator: getOnboardingInitiator(state), - } -} const mapDispatchToProps = dispatch => { return { @@ -19,4 +12,4 @@ const mapDispatchToProps = dispatch => { } } -export default connect(mapStateToProps, mapDispatchToProps)(RevealSeedPhrase) +export default connect(null, mapDispatchToProps)(RevealSeedPhrase) diff --git a/ui/app/pages/home/home.component.js b/ui/app/pages/home/home.component.js index 84179c568..e51c82177 100644 --- a/ui/app/pages/home/home.component.js +++ b/ui/app/pages/home/home.component.js @@ -8,13 +8,13 @@ import DaiMigrationNotification from '../../components/app/dai-migration-compone import MultipleNotifications from '../../components/app/multiple-notifications' import WalletView from '../../components/app/wallet-view' import TransactionView from '../../components/app/transaction-view' +import ProviderApproval from '../provider-approval' import { RESTORE_VAULT_ROUTE, CONFIRM_TRANSACTION_ROUTE, CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE, INITIALIZE_BACKUP_SEED_PHRASE_ROUTE, - CONNECT_ROUTE, } from '../../helpers/constants/routes' export default class Home extends PureComponent { @@ -23,6 +23,7 @@ export default class Home extends PureComponent { } static defaultProps = { + unsetMigratedPrivacyMode: null, hasDaiV1Token: false, } @@ -31,6 +32,9 @@ export default class Home extends PureComponent { forgottenPassword: PropTypes.bool, suggestedTokens: PropTypes.object, unconfirmedTransactionsCount: PropTypes.number, + providerRequests: PropTypes.array, + showPrivacyModeNotification: PropTypes.bool.isRequired, + unsetMigratedPrivacyMode: PropTypes.func, shouldShowSeedPhraseReminder: PropTypes.bool, isPopup: PropTypes.bool, threeBoxSynced: PropTypes.bool, @@ -42,20 +46,14 @@ export default class Home extends PureComponent { setShowRestorePromptToFalse: PropTypes.func, threeBoxLastUpdated: PropTypes.number, hasDaiV1Token: PropTypes.bool, - hasPermissionRequests: PropTypes.bool, } componentWillMount () { const { history, unconfirmedTransactionsCount = 0, - hasPermissionRequests, } = this.props - if (hasPermissionRequests) { - history.push(CONNECT_ROUTE) - } - if (unconfirmedTransactionsCount > 0) { history.push(CONFIRM_TRANSACTION_ROUTE) } @@ -89,8 +87,11 @@ export default class Home extends PureComponent { const { t } = this.context const { forgottenPassword, + providerRequests, history, hasDaiV1Token, + showPrivacyModeNotification, + unsetMigratedPrivacyMode, shouldShowSeedPhraseReminder, isPopup, selectedAddress, @@ -105,6 +106,11 @@ export default class Home extends PureComponent { return } + if (providerRequests && providerRequests.length > 0) { + return ( + + ) + } return (
    @@ -116,45 +122,58 @@ export default class Home extends PureComponent { ? ( + { + showPrivacyModeNotification + ? { + unsetMigratedPrivacyMode() + window.open('https://medium.com/metamask/42549d4870fa', '_blank', 'noopener') + }} + ignoreText={t('dismiss')} + onIgnore={() => { + unsetMigratedPrivacyMode() + }} + key="home-privacyModeDefault" + /> + : null + } { shouldShowSeedPhraseReminder - ? ( - { - if (isPopup) { - global.platform.openExtensionInBrowser(INITIALIZE_BACKUP_SEED_PHRASE_ROUTE) - } else { - history.push(INITIALIZE_BACKUP_SEED_PHRASE_ROUTE) - } - }} - infoText={t('backupApprovalInfo')} - key="home-backupApprovalNotice" - /> - ) + ? { + if (isPopup) { + global.platform.openExtensionInBrowser(INITIALIZE_BACKUP_SEED_PHRASE_ROUTE) + } else { + history.push(INITIALIZE_BACKUP_SEED_PHRASE_ROUTE) + } + }} + infoText={t('backupApprovalInfo')} + key="home-backupApprovalNotice" + /> : null } { threeBoxLastUpdated && showRestorePrompt - ? ( - { - restoreFromThreeBox(selectedAddress) - .then(() => { - turnThreeBoxSyncingOn() - }) - }} - onIgnore={() => { - setShowRestorePromptToFalse() - }} - key="home-privacyModeDefault" - /> - ) + ? { + restoreFromThreeBox(selectedAddress) + .then(() => { + turnThreeBoxSyncingOn() + }) + }} + onIgnore={() => { + setShowRestorePromptToFalse() + }} + key="home-privacyModeDefault" + /> : null } { diff --git a/ui/app/pages/home/home.container.js b/ui/app/pages/home/home.container.js index f14bb7996..4a2106a55 100644 --- a/ui/app/pages/home/home.container.js +++ b/ui/app/pages/home/home.container.js @@ -3,8 +3,9 @@ import { compose } from 'recompose' import { connect } from 'react-redux' import { withRouter } from 'react-router-dom' import { unconfirmedTransactionsCountSelector } from '../../selectors/confirm-transaction' -import { getCurrentEthBalance, getDaiV1Token, hasPermissionRequests } from '../../selectors/selectors' +import { getCurrentEthBalance, getDaiV1Token } from '../../selectors/selectors' import { + unsetMigratedPrivacyMode, restoreFromThreeBox, turnThreeBoxSyncingOn, getThreeBoxLastUpdated, @@ -15,9 +16,11 @@ import { getEnvironmentType } from '../../../../app/scripts/lib/util' import { ENVIRONMENT_TYPE_POPUP } from '../../../../app/scripts/lib/enums' const mapStateToProps = state => { - const { activeTab, metamask, appState } = state + const { metamask, appState } = state const { suggestedTokens, + providerRequests, + migratedPrivacyMode, seedPhraseBackedUp, tokens, threeBoxSynced, @@ -33,7 +36,8 @@ const mapStateToProps = state => { forgottenPassword, suggestedTokens, unconfirmedTransactionsCount: unconfirmedTransactionsCountSelector(state), - activeTab, + providerRequests, + showPrivacyModeNotification: migratedPrivacyMode, shouldShowSeedPhraseReminder: !seedPhraseBackedUp && (parseInt(accountBalance, 16) > 0 || tokens.length > 0), isPopup, threeBoxSynced, @@ -41,11 +45,11 @@ const mapStateToProps = state => { selectedAddress, threeBoxLastUpdated, hasDaiV1Token: Boolean(getDaiV1Token(state)), - hasPermissionRequests: hasPermissionRequests(state), } } const mapDispatchToProps = (dispatch) => ({ + unsetMigratedPrivacyMode: () => dispatch(unsetMigratedPrivacyMode()), turnThreeBoxSyncingOn: () => dispatch(turnThreeBoxSyncingOn()), setupThreeBox: () => { dispatch(getThreeBoxLastUpdated()) diff --git a/ui/app/pages/index.scss b/ui/app/pages/index.scss index deb6bce45..d79b7c28d 100644 --- a/ui/app/pages/index.scss +++ b/ui/app/pages/index.scss @@ -13,7 +13,3 @@ @import 'keychains/index'; @import 'confirm-approve/index'; - -@import 'permissions-connect/index'; - -@import 'connected-sites/index'; diff --git a/ui/app/pages/keychains/reveal-seed.js b/ui/app/pages/keychains/reveal-seed.js index 0e842594f..e83e3fd98 100644 --- a/ui/app/pages/keychains/reveal-seed.js +++ b/ui/app/pages/keychains/reveal-seed.js @@ -1,6 +1,7 @@ -import React, { Component } from 'react' +const { Component } = require('react') const { connect } = require('react-redux') const PropTypes = require('prop-types') +const h = require('react-hyperscript') const classnames = require('classnames') const { requestRevealSeedWords } = require('../../store/actions') @@ -13,11 +14,15 @@ const PASSWORD_PROMPT_SCREEN = 'PASSWORD_PROMPT_SCREEN' const REVEAL_SEED_SCREEN = 'REVEAL_SEED_SCREEN' class RevealSeedPage extends Component { - state = { - screen: PASSWORD_PROMPT_SCREEN, - password: '', - seedWords: null, - error: null, + constructor (props) { + super(props) + + this.state = { + screen: PASSWORD_PROMPT_SCREEN, + password: '', + seedWords: null, + error: null, + } } componentDidMount () { @@ -37,17 +42,15 @@ class RevealSeedPage extends Component { renderWarning () { return ( -
    - -
    -
    - {this.context.t('revealSeedWordsWarningTitle')} -
    -
    - {this.context.t('revealSeedWordsWarning')} -
    -
    -
    + h('.page-container__warning-container', [ + h('img.page-container__warning-icon', { + src: 'images/warning.svg', + }), + h('.page-container__warning-message', [ + h('.page-container__warning-title', [this.context.t('revealSeedWordsWarningTitle')]), + h('div', [this.context.t('revealSeedWordsWarning')]), + ]), + ]) ) } @@ -61,25 +64,24 @@ class RevealSeedPage extends Component { const { t } = this.context return ( -
    this.handleSubmit(event)}> - -
    - this.setState({ password: event.target.value })} - className={classnames('form-control', { 'form-control--error': this.state.error })} - /> -
    - { - this.state.error && ( -
    - {this.state.error} -
    - )} -
    + h('form', { + onSubmit: event => this.handleSubmit(event), + }, [ + h('label.input-label', { + htmlFor: 'password-box', + }, t('enterPasswordContinue')), + h('.input-group', [ + h('input.form-control', { + type: 'password', + placeholder: t('password'), + id: 'password-box', + value: this.state.password, + onChange: event => this.setState({ password: event.target.value }), + className: classnames({ 'form-control--error': this.state.error }), + }), + ]), + this.state.error && h('.reveal-seed__error', this.state.error), + ]) ) } @@ -87,10 +89,13 @@ class RevealSeedPage extends Component { const { t } = this.context return ( -
    - - -
    + h('div', [ + h('label.reveal-seed__label', t('yourPrivateSeedPhrase')), + h(ExportTextContainer, { + text: this.state.seedWords, + filename: t('metamaskSeedWords'), + }), + ]) ) } @@ -102,64 +107,54 @@ class RevealSeedPage extends Component { renderPasswordPromptFooter () { return ( -
    -
    - - -
    -
    + h('.page-container__footer', [ + h('header', [ + h(Button, { + type: 'default', + large: true, + className: 'page-container__footer-button', + onClick: () => this.props.history.push(DEFAULT_ROUTE), + }, this.context.t('cancel')), + h(Button, { + type: 'secondary', + large: true, + className: 'page-container__footer-button', + onClick: event => this.handleSubmit(event), + disabled: this.state.password === '', + }, this.context.t('next')), + ]), + ]) ) } renderRevealSeedFooter () { return ( -
    - -
    + h('.page-container__footer', [ + h(Button, { + type: 'default', + large: true, + className: 'page-container__footer-button', + onClick: () => this.props.history.push(DEFAULT_ROUTE), + }, this.context.t('close')), + ]) ) } render () { return ( -
    -
    -
    - {this.context.t('revealSeedWordsTitle')} -
    -
    - {this.context.t('revealSeedWordsDescription')} -
    -
    -
    - {this.renderWarning()} -
    - {this.renderContent()} -
    -
    - {this.renderFooter()} -
    + h('.page-container', [ + h('.page-container__header', [ + h('.page-container__title', this.context.t('revealSeedWordsTitle')), + h('.page-container__subtitle', this.context.t('revealSeedWordsDescription')), + ]), + h('.page-container__content', [ + this.renderWarning(), + h('.reveal-seed__content', [ + this.renderContent(), + ]), + ]), + this.renderFooter(), + ]) ) } } diff --git a/ui/app/pages/mobile-sync/index.js b/ui/app/pages/mobile-sync/index.js index c7df5ede5..bd2385808 100644 --- a/ui/app/pages/mobile-sync/index.js +++ b/ui/app/pages/mobile-sync/index.js @@ -1 +1,415 @@ -export { default } from './mobile-sync.container' +const { Component } = require('react') +const { connect } = require('react-redux') +const PropTypes = require('prop-types') +const h = require('react-hyperscript') +const classnames = require('classnames') +const PubNub = require('pubnub') + +const { requestRevealSeedWords, fetchInfoToSync } = require('../../store/actions') +const { DEFAULT_ROUTE } = require('../../helpers/constants/routes') +const actions = require('../../store/actions') + +const qrCode = require('qrcode-generator') + +import Button from '../../components/ui/button' +import LoadingScreen from '../../components/ui/loading-screen' + +const PASSWORD_PROMPT_SCREEN = 'PASSWORD_PROMPT_SCREEN' +const REVEAL_SEED_SCREEN = 'REVEAL_SEED_SCREEN' +const KEYS_GENERATION_TIME = 30000 + +class MobileSyncPage extends Component { + static propTypes = { + history: PropTypes.object, + selectedAddress: PropTypes.string, + displayWarning: PropTypes.func, + fetchInfoToSync: PropTypes.func, + requestRevealSeedWords: PropTypes.func, + } + + constructor (props) { + super(props) + + this.state = { + screen: PASSWORD_PROMPT_SCREEN, + password: '', + seedWords: null, + error: null, + syncing: false, + completed: false, + channelName: undefined, + cipherKey: undefined, + } + + this.syncing = false + } + + componentDidMount () { + const passwordBox = document.getElementById('password-box') + if (passwordBox) { + passwordBox.focus() + } + } + + handleSubmit (event) { + event.preventDefault() + this.setState({ seedWords: null, error: null }) + this.props.requestRevealSeedWords(this.state.password) + .then(seedWords => { + this.startKeysGeneration() + this.setState({ seedWords, screen: REVEAL_SEED_SCREEN }) + }) + .catch(error => this.setState({ error: error.message })) + } + + startKeysGeneration () { + this.handle && clearTimeout(this.handle) + this.disconnectWebsockets() + this.generateCipherKeyAndChannelName() + this.initWebsockets() + this.handle = setTimeout(() => { + this.startKeysGeneration() + }, KEYS_GENERATION_TIME) + } + + generateCipherKeyAndChannelName () { + this.cipherKey = `${this.props.selectedAddress.substr(-4)}-${PubNub.generateUUID()}` + this.channelName = `mm-${PubNub.generateUUID()}` + this.setState({cipherKey: this.cipherKey, channelName: this.channelName}) + } + + initWithCipherKeyAndChannelName (cipherKey, channelName) { + this.cipherKey = cipherKey + this.channelName = channelName + } + + initWebsockets () { + // Make sure there are no existing listeners + this.disconnectWebsockets() + + this.pubnub = new PubNub({ + subscribeKey: process.env.PUBNUB_SUB_KEY, + publishKey: process.env.PUBNUB_PUB_KEY, + cipherKey: this.cipherKey, + ssl: true, + }) + + this.pubnubListener = { + message: (data) => { + const {channel, message} = data + // handle message + if (channel !== this.channelName || !message) { + return false + } + + if (message.event === 'start-sync') { + this.startSyncing() + } else if (message.event === 'connection-info') { + this.handle && clearTimeout(this.handle) + this.disconnectWebsockets() + this.initWithCipherKeyAndChannelName(message.cipher, message.channel) + this.initWebsockets() + } else if (message.event === 'end-sync') { + this.disconnectWebsockets() + this.setState({syncing: false, completed: true}) + } + }, + } + + this.pubnub.addListener(this.pubnubListener) + + this.pubnub.subscribe({ + channels: [this.channelName], + withPresence: false, + }) + + } + + disconnectWebsockets () { + if (this.pubnub && this.pubnubListener) { + this.pubnub.removeListener(this.pubnubListener) + } + } + + // Calculating a PubNub Message Payload Size. + calculatePayloadSize (channel, message) { + return encodeURIComponent( + channel + JSON.stringify(message) + ).length + 100 + } + + chunkString (str, size) { + const numChunks = Math.ceil(str.length / size) + const chunks = new Array(numChunks) + for (let i = 0, o = 0; i < numChunks; ++i, o += size) { + chunks[i] = str.substr(o, size) + } + return chunks + } + + notifyError (errorMsg) { + return new Promise((resolve, reject) => { + this.pubnub.publish( + { + message: { + event: 'error-sync', + data: errorMsg, + }, + channel: this.channelName, + sendByPost: false, // true to send via post + storeInHistory: false, + }, + (status, response) => { + if (!status.error) { + resolve() + } else { + reject(response) + } + }) + }) + } + + async startSyncing () { + if (this.syncing) return false + this.syncing = true + this.setState({syncing: true}) + + const { accounts, network, preferences, transactions } = await this.props.fetchInfoToSync() + + const allDataStr = JSON.stringify({ + accounts, + network, + preferences, + transactions, + udata: { + pwd: this.state.password, + seed: this.state.seedWords, + }, + }) + + const chunks = this.chunkString(allDataStr, 17000) + const totalChunks = chunks.length + try { + for (let i = 0; i < totalChunks; i++) { + await this.sendMessage(chunks[i], i + 1, totalChunks) + } + } catch (e) { + this.props.displayWarning('Sync failed :(') + this.setState({syncing: false}) + this.syncing = false + this.notifyError(e.toString()) + } + } + + sendMessage (data, pkg, count) { + return new Promise((resolve, reject) => { + this.pubnub.publish( + { + message: { + event: 'syncing-data', + data, + totalPkg: count, + currentPkg: pkg, + }, + channel: this.channelName, + sendByPost: false, // true to send via post + storeInHistory: false, + }, + (status, response) => { + if (!status.error) { + resolve() + } else { + reject(response) + } + } + ) + }) + } + + + componentWillUnmount () { + this.disconnectWebsockets() + } + + renderWarning (text) { + return ( + h('.page-container__warning-container', [ + h('.page-container__warning-message', [ + h('div', [text]), + ]), + ]) + ) + } + + renderContent () { + const { t } = this.context + + if (this.state.syncing) { + return h(LoadingScreen, {loadingMessage: 'Sync in progress'}) + } + + if (this.state.completed) { + return h('div.reveal-seed__content', {}, + h('label.reveal-seed__label', { + style: { + width: '100%', + textAlign: 'center', + }, + }, t('syncWithMobileComplete')), + ) + } + + return this.state.screen === PASSWORD_PROMPT_SCREEN + ? h('div', {}, [ + this.renderWarning(this.context.t('mobileSyncText')), + h('.reveal-seed__content', [ + this.renderPasswordPromptContent(), + ]), + ]) + : h('div', {}, [ + this.renderWarning(this.context.t('syncWithMobileBeCareful')), + h('.reveal-seed__content', [ this.renderRevealSeedContent() ]), + ]) + } + + renderPasswordPromptContent () { + const { t } = this.context + + return ( + h('form', { + onSubmit: event => this.handleSubmit(event), + }, [ + h('label.input-label', { + htmlFor: 'password-box', + }, t('enterPasswordContinue')), + h('.input-group', [ + h('input.form-control', { + type: 'password', + placeholder: t('password'), + id: 'password-box', + value: this.state.password, + onChange: event => this.setState({ password: event.target.value }), + className: classnames({ 'form-control--error': this.state.error }), + }), + ]), + this.state.error && h('.reveal-seed__error', this.state.error), + ]) + ) + } + + renderRevealSeedContent () { + + const qrImage = qrCode(0, 'M') + qrImage.addData(`metamask-sync:${this.state.channelName}|@|${this.state.cipherKey}`) + qrImage.make() + + const { t } = this.context + return ( + h('div', [ + h('label.reveal-seed__label', { + style: { + width: '100%', + textAlign: 'center', + }, + }, t('syncWithMobileScanThisCode')), + h('.div.qr-wrapper', { + style: { + display: 'flex', + justifyContent: 'center', + }, + dangerouslySetInnerHTML: { + __html: qrImage.createTableTag(4), + }, + }), + ]) + ) + } + + renderFooter () { + return this.state.screen === PASSWORD_PROMPT_SCREEN + ? this.renderPasswordPromptFooter() + : this.renderRevealSeedFooter() + } + + renderPasswordPromptFooter () { + return ( + h('div.new-account-import-form__buttons', {style: {padding: 30}}, [ + + h(Button, { + type: 'default', + large: true, + className: 'new-account-create-form__button', + onClick: () => this.props.history.push(DEFAULT_ROUTE), + }, this.context.t('cancel')), + + h(Button, { + type: 'secondary', + large: true, + className: 'new-account-create-form__button', + onClick: event => this.handleSubmit(event), + disabled: this.state.password === '', + }, this.context.t('next')), + ]) + ) + } + + renderRevealSeedFooter () { + return ( + h('.page-container__footer', {style: {padding: 30}}, [ + h(Button, { + type: 'default', + large: true, + className: 'page-container__footer-button', + onClick: () => this.props.history.push(DEFAULT_ROUTE), + }, this.context.t('close')), + ]) + ) + } + + render () { + return ( + h('.page-container', [ + h('.page-container__header', [ + h('.page-container__title', this.context.t('syncWithMobileTitle')), + this.state.screen === PASSWORD_PROMPT_SCREEN ? h('.page-container__subtitle', this.context.t('syncWithMobileDesc')) : null, + this.state.screen === PASSWORD_PROMPT_SCREEN ? h('.page-container__subtitle', this.context.t('syncWithMobileDescNewUsers')) : null, + ]), + h('.page-container__content', [ + this.renderContent(), + ]), + this.renderFooter(), + ]) + ) + } +} + +MobileSyncPage.propTypes = { + requestRevealSeedWords: PropTypes.func, + fetchInfoToSync: PropTypes.func, + history: PropTypes.object, +} + +MobileSyncPage.contextTypes = { + t: PropTypes.func, +} + +const mapDispatchToProps = dispatch => { + return { + requestRevealSeedWords: password => dispatch(requestRevealSeedWords(password)), + fetchInfoToSync: () => dispatch(fetchInfoToSync()), + displayWarning: (message) => dispatch(actions.displayWarning(message || null)), + } + +} + +const mapStateToProps = state => { + const { + metamask: { selectedAddress }, + } = state + + return { + selectedAddress, + } +} + +module.exports = connect(mapStateToProps, mapDispatchToProps)(MobileSyncPage) diff --git a/ui/app/pages/mobile-sync/mobile-sync.component.js b/ui/app/pages/mobile-sync/mobile-sync.component.js deleted file mode 100644 index 9510afa80..000000000 --- a/ui/app/pages/mobile-sync/mobile-sync.component.js +++ /dev/null @@ -1,436 +0,0 @@ -import React, { Component } from 'react' - -const PropTypes = require('prop-types') -const classnames = require('classnames') -const PubNub = require('pubnub') -const qrCode = require('qrcode-generator') - -const { DEFAULT_ROUTE } = require('../../helpers/constants/routes') - -import Button from '../../components/ui/button' -import LoadingScreen from '../../components/ui/loading-screen' - -const PASSWORD_PROMPT_SCREEN = 'PASSWORD_PROMPT_SCREEN' -const REVEAL_SEED_SCREEN = 'REVEAL_SEED_SCREEN' -const KEYS_GENERATION_TIME = 30000 - -export default class MobileSyncPage extends Component { - static contextTypes = { - t: PropTypes.func, - } - - static propTypes = { - history: PropTypes.object.isRequired, - selectedAddress: PropTypes.string.isRequired, - displayWarning: PropTypes.func.isRequired, - fetchInfoToSync: PropTypes.func.isRequired, - requestRevealSeedWords: PropTypes.func.isRequired, - } - - - state = { - screen: PASSWORD_PROMPT_SCREEN, - password: '', - seedWords: null, - error: null, - syncing: false, - completed: false, - channelName: undefined, - cipherKey: undefined, - } - - syncing = false - - componentDidMount () { - const passwordBox = document.getElementById('password-box') - if (passwordBox) { - passwordBox.focus() - } - } - - handleSubmit (event) { - event.preventDefault() - this.setState({ seedWords: null, error: null }) - this.props.requestRevealSeedWords(this.state.password) - .then(seedWords => { - this.startKeysGeneration() - this.setState({ seedWords, screen: REVEAL_SEED_SCREEN }) - }) - .catch(error => this.setState({ error: error.message })) - } - - startKeysGeneration () { - this.handle && clearTimeout(this.handle) - this.disconnectWebsockets() - this.generateCipherKeyAndChannelName() - this.initWebsockets() - this.handle = setTimeout(() => { - this.startKeysGeneration() - }, KEYS_GENERATION_TIME) - } - - generateCipherKeyAndChannelName () { - this.cipherKey = `${this.props.selectedAddress.substr(-4)}-${PubNub.generateUUID()}` - this.channelName = `mm-${PubNub.generateUUID()}` - this.setState({cipherKey: this.cipherKey, channelName: this.channelName}) - } - - initWithCipherKeyAndChannelName (cipherKey, channelName) { - this.cipherKey = cipherKey - this.channelName = channelName - } - - initWebsockets () { - // Make sure there are no existing listeners - this.disconnectWebsockets() - - this.pubnub = new PubNub({ - subscribeKey: process.env.PUBNUB_SUB_KEY, - publishKey: process.env.PUBNUB_PUB_KEY, - cipherKey: this.cipherKey, - ssl: true, - }) - - this.pubnubListener = { - message: (data) => { - const {channel, message} = data - // handle message - if (channel !== this.channelName || !message) { - return false - } - - if (message.event === 'start-sync') { - this.startSyncing() - } else if (message.event === 'connection-info') { - this.handle && clearTimeout(this.handle) - this.disconnectWebsockets() - this.initWithCipherKeyAndChannelName(message.cipher, message.channel) - this.initWebsockets() - } else if (message.event === 'end-sync') { - this.disconnectWebsockets() - this.setState({syncing: false, completed: true}) - } - }, - } - - this.pubnub.addListener(this.pubnubListener) - - this.pubnub.subscribe({ - channels: [this.channelName], - withPresence: false, - }) - - } - - disconnectWebsockets () { - if (this.pubnub && this.pubnubListener) { - this.pubnub.removeListener(this.pubnubListener) - } - } - - // Calculating a PubNub Message Payload Size. - calculatePayloadSize (channel, message) { - return encodeURIComponent( - channel + JSON.stringify(message) - ).length + 100 - } - - chunkString (str, size) { - const numChunks = Math.ceil(str.length / size) - const chunks = new Array(numChunks) - for (let i = 0, o = 0; i < numChunks; ++i, o += size) { - chunks[i] = str.substr(o, size) - } - return chunks - } - - notifyError (errorMsg) { - return new Promise((resolve, reject) => { - this.pubnub.publish( - { - message: { - event: 'error-sync', - data: errorMsg, - }, - channel: this.channelName, - sendByPost: false, // true to send via post - storeInHistory: false, - }, - (status, response) => { - if (!status.error) { - resolve() - } else { - reject(response) - } - }) - }) - } - - async startSyncing () { - if (this.syncing) { - return false - } - this.syncing = true - this.setState({syncing: true}) - - const { accounts, network, preferences, transactions } = await this.props.fetchInfoToSync() - - const allDataStr = JSON.stringify({ - accounts, - network, - preferences, - transactions, - udata: { - pwd: this.state.password, - seed: this.state.seedWords, - }, - }) - - const chunks = this.chunkString(allDataStr, 17000) - const totalChunks = chunks.length - try { - for (let i = 0; i < totalChunks; i++) { - await this.sendMessage(chunks[i], i + 1, totalChunks) - } - } catch (e) { - this.props.displayWarning('Sync failed :(') - this.setState({syncing: false}) - this.syncing = false - this.notifyError(e.toString()) - } - } - - sendMessage (data, pkg, count) { - return new Promise((resolve, reject) => { - this.pubnub.publish( - { - message: { - event: 'syncing-data', - data, - totalPkg: count, - currentPkg: pkg, - }, - channel: this.channelName, - sendByPost: false, // true to send via post - storeInHistory: false, - }, - (status, response) => { - if (!status.error) { - resolve() - } else { - reject(response) - } - } - ) - }) - } - - - componentWillUnmount () { - this.disconnectWebsockets() - } - - renderWarning (text) { - return ( -
    -
    -
    {text}
    -
    -
    - ) - } - - renderContent () { - const { syncing, completed, screen } = this.state - const { t } = this.context - - if (syncing) { - return ( - - ) - } - - if (completed) { - return ( -
    - -
    - ) - } - - return screen === PASSWORD_PROMPT_SCREEN - ? ( -
    - {this.renderWarning(this.context.t('mobileSyncText'))} -
    - {this.renderPasswordPromptContent()} -
    -
    - ) - : ( -
    - {this.renderWarning(this.context.t('syncWithMobileBeCareful'))} -
    - {this.renderRevealSeedContent()} -
    -
    - ) - } - - renderPasswordPromptContent () { - const { t } = this.context - - return ( -
    this.handleSubmit(event)}> - -
    - this.setState({ password: event.target.value })} - className={classnames('form-control', { - 'form-control--error': this.state.error, - })} - /> -
    - {this.state.error && ( -
    - {this.state.error} -
    - )} -
    - ) - } - - renderRevealSeedContent () { - const qrImage = qrCode(0, 'M') - qrImage.addData(`metamask-sync:${this.state.channelName}|@|${this.state.cipherKey}`) - qrImage.make() - - const { t } = this.context - return ( -
    - -
    -
    - ) - } - - renderFooter () { - return this.state.screen === PASSWORD_PROMPT_SCREEN - ? this.renderPasswordPromptFooter() - : this.renderRevealSeedFooter() - } - - renderPasswordPromptFooter () { - const { t } = this.context - const { history } = this.props - const { password } = this.state - - return ( -
    - - -
    - ) - } - - renderRevealSeedFooter () { - const { t } = this.context - const { history } = this.props - - return ( -
    - -
    - ) - } - - render () { - const { t } = this.context - const { screen } = this.state - - return ( -
    -
    -
    - {t('syncWithMobileTitle')} -
    - { - screen === PASSWORD_PROMPT_SCREEN - ? ( -
    - {t('syncWithMobileDesc')} -
    - ) - : null - } - { - screen === PASSWORD_PROMPT_SCREEN - ? ( -
    - {t('syncWithMobileDescNewUsers')} -
    - ) - : null - } -
    -
    - {this.renderContent()} -
    - {this.renderFooter()} -
    - ) - } -} diff --git a/ui/app/pages/mobile-sync/mobile-sync.container.js b/ui/app/pages/mobile-sync/mobile-sync.container.js deleted file mode 100644 index 915563841..000000000 --- a/ui/app/pages/mobile-sync/mobile-sync.container.js +++ /dev/null @@ -1,25 +0,0 @@ -import { connect } from 'react-redux' -import { displayWarning, requestRevealSeedWords, fetchInfoToSync } from '../../store/actions' -import MobileSyncPage from './mobile-sync.component' - -const mapDispatchToProps = (dispatch) => { - return { - requestRevealSeedWords: password => dispatch(requestRevealSeedWords(password)), - fetchInfoToSync: () => dispatch(fetchInfoToSync()), - displayWarning: (message) => dispatch(displayWarning(message || null)), - } -} - -const mapStateToProps = state => { - const { - metamask: { - selectedAddress, - }, - } = state - - return { - selectedAddress, - } -} - -module.exports = connect(mapStateToProps, mapDispatchToProps)(MobileSyncPage) diff --git a/ui/app/pages/permissions-connect/choose-account/choose-account.component.js b/ui/app/pages/permissions-connect/choose-account/choose-account.component.js deleted file mode 100644 index 1a3a8fc49..000000000 --- a/ui/app/pages/permissions-connect/choose-account/choose-account.component.js +++ /dev/null @@ -1,108 +0,0 @@ -import PropTypes from 'prop-types' -import React, { Component } from 'react' -import Identicon from '../../../components/ui/identicon' -import { PRIMARY } from '../../../helpers/constants/common' -import UserPreferencedCurrencyDisplay from '../../../components/app/user-preferenced-currency-display' - -export default class ChooseAccount extends Component { - static propTypes = { - accounts: PropTypes.arrayOf(PropTypes.shape({ - address: PropTypes.string, - addressLabel: PropTypes.string, - lastConnectedDate: PropTypes.string, - balance: PropTypes.string, - })).isRequired, - originName: PropTypes.string.isRequired, - selectAccount: PropTypes.func.isRequired, - selectNewAccountViaModal: PropTypes.func.isRequired, - nativeCurrency: PropTypes.string.isRequired, - addressLastConnectedMap: PropTypes.object, - cancelPermissionsRequest: PropTypes.func.isRequired, - permissionsRequestId: PropTypes.string.isRequired, - } - - static defaultProps = { - addressLastConnectedMap: {}, - } - - static contextTypes = { - t: PropTypes.func, - }; - - renderAccountsList = () => { - const { accounts, selectAccount, nativeCurrency, addressLastConnectedMap } = this.props - return ( -
    - { - accounts.map((account, index) => { - const { address, addressLabel, balance } = account - return ( -
    selectAccount(address) } - className="permissions-connect-choose-account__account" - > -
    - -
    -
    { addressLabel }
    - -
    -
    - { addressLastConnectedMap[address] - ? ( -
    - { this.context.t('lastConnected') } - { addressLastConnectedMap[address] } -
    - ) - : null - } -
    - ) - }) - } -
    - ) - } - - render () { - const { originName, selectNewAccountViaModal, permissionsRequestId, cancelPermissionsRequest } = this.props - const { t } = this.context - return ( -
    -
    - { t('chooseAnAcount') } -
    -
    - { t('toConnectWith', [originName]) } -
    - { this.renderAccountsList() } -
    -
    cancelPermissionsRequest(permissionsRequestId) } - className="permissions-connect-choose-account__cancel" - > - { t('cancel') } -
    -
    selectNewAccountViaModal() } - className="permissions-connect-choose-account__new-account" - > - { t('newAccount') } -
    -
    -
    - ) - } -} diff --git a/ui/app/pages/permissions-connect/choose-account/index.js b/ui/app/pages/permissions-connect/choose-account/index.js deleted file mode 100644 index 10b4cd224..000000000 --- a/ui/app/pages/permissions-connect/choose-account/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './choose-account.component' diff --git a/ui/app/pages/permissions-connect/choose-account/index.scss b/ui/app/pages/permissions-connect/choose-account/index.scss deleted file mode 100644 index 12c124b18..000000000 --- a/ui/app/pages/permissions-connect/choose-account/index.scss +++ /dev/null @@ -1,97 +0,0 @@ -.permissions-connect-choose-account { - display: flex; - flex-direction: column; - margin-top: 40px; - width: 100%; - align-items: center; - - &__title { - @extend %header--18; - } - - &__text { - @extend %content-text; - line-height: 25px; - } - - &__accounts-list { - width: 393px; - border: 1px solid #D0D5DA; - box-sizing: border-box; - border-radius: 8px; - margin-top: 36px; - - @media screen and (max-width: 575px) { - width: 100%; - } - } - - &__account-info-wrapper { - display: flex; - justify-content: flex-start; - } - - &__account { - display: flex; - align-items: center; - padding: 16px; - border-bottom: 1px solid #D2D8DD; - justify-content: space-between; - - &:last-of-type { - border-bottom: none; - } - - &:hover { - background: aliceblue; - cursor: pointer; - } - - &__info { - display: flex; - flex-direction: column; - margin-left: 16px; - } - - &__label { - @extend %header--18; - line-height: 25px; - color: #000000; - } - - &__balance { - @extend %content-text; - line-height: 17px; - color: #6A737D; - } - - &__last-connected { - @extend %content-text; - font-size: 10px; - line-height: 140.62%; - display: flex; - flex-direction: column; - align-items: flex-end; - color: #037DD6; - } - } - - &__new-account, &__cancel { - @extend %content-text; - line-height: 20px; - color: #037DD6; - margin-top: 24px; - cursor: pointer; - } - - &__cancel { - color: $Red-400; - } - - &__bottom-buttons { - display: flex; - justify-content: space-around; - width: 393px; - } - -} \ No newline at end of file diff --git a/ui/app/pages/permissions-connect/index.js b/ui/app/pages/permissions-connect/index.js deleted file mode 100644 index 06798a2f6..000000000 --- a/ui/app/pages/permissions-connect/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './permissions-connect.container' diff --git a/ui/app/pages/permissions-connect/index.scss b/ui/app/pages/permissions-connect/index.scss deleted file mode 100644 index fe2f5182a..000000000 --- a/ui/app/pages/permissions-connect/index.scss +++ /dev/null @@ -1,11 +0,0 @@ -@import 'permissions-connect-header/index'; - -@import 'permissions-connect-footer/index'; - -@import 'choose-account/index'; - -.permissions-connect { - width: 100%; - position: relative; - background: white; -} diff --git a/ui/app/pages/permissions-connect/permissions-connect-footer/index.js b/ui/app/pages/permissions-connect/permissions-connect-footer/index.js deleted file mode 100644 index 8096e1e3d..000000000 --- a/ui/app/pages/permissions-connect/permissions-connect-footer/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './permissions-connect-footer.component' diff --git a/ui/app/pages/permissions-connect/permissions-connect-footer/index.scss b/ui/app/pages/permissions-connect/permissions-connect-footer/index.scss deleted file mode 100644 index ff805e541..000000000 --- a/ui/app/pages/permissions-connect/permissions-connect-footer/index.scss +++ /dev/null @@ -1,27 +0,0 @@ -.permissions-connect-footer { - display: flex; - flex-direction: column; - width: 100%; - align-items: center; - position: absolute; - bottom: 50px; - - @media screen and (max-width: 575px) { - bottom: 5px; - } - - &__text { - @extend %content-text; - font-size: 12px; - line-height: 17px; - color: #6A737D; - display: flex; - margin-top: 10px; - - &--link { - color: #037DD6; - margin-left: 4px; - cursor: pointer; - } - } -} \ No newline at end of file diff --git a/ui/app/pages/permissions-connect/permissions-connect-footer/permissions-connect-footer.component.js b/ui/app/pages/permissions-connect/permissions-connect-footer/permissions-connect-footer.component.js deleted file mode 100644 index b8fad97a3..000000000 --- a/ui/app/pages/permissions-connect/permissions-connect-footer/permissions-connect-footer.component.js +++ /dev/null @@ -1,27 +0,0 @@ -import PropTypes from 'prop-types' -import React, { Component } from 'react' - -export default class PermissionsConnectFooter extends Component { - static contextTypes = { - t: PropTypes.func, - } - - render () { - const { t } = this.context - return ( -
    - -
    -
    { t('onlyConnectTrust') }
    -
    { - global.platform.openWindow({ url: 'https://medium.com/metamask/privacy-mode-is-now-enabled-by-default-1c1c957f4d57' }) - }} - >{ t('learnAboutRisks') } -
    -
    -
    - ) - } -} diff --git a/ui/app/pages/permissions-connect/permissions-connect-header/index.js b/ui/app/pages/permissions-connect/permissions-connect-header/index.js deleted file mode 100644 index e9de1a06a..000000000 --- a/ui/app/pages/permissions-connect/permissions-connect-header/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './permissions-connect-header.component' diff --git a/ui/app/pages/permissions-connect/permissions-connect-header/index.scss b/ui/app/pages/permissions-connect/permissions-connect-header/index.scss deleted file mode 100644 index ca239bf17..000000000 --- a/ui/app/pages/permissions-connect/permissions-connect-header/index.scss +++ /dev/null @@ -1,15 +0,0 @@ -.permissions-connect-header { - margin-top: 26px; - margin-left: 20px; - display: flex; - justify-content: space-between; - align-items: flex-end; - - &__page-count { - @extend %content-text; - margin-right: 30px; - font-size: 12px; - line-height: 17px; - color: #6A737D; - } -} \ No newline at end of file diff --git a/ui/app/pages/permissions-connect/permissions-connect-header/permissions-connect-header.component.js b/ui/app/pages/permissions-connect/permissions-connect-header/permissions-connect-header.component.js deleted file mode 100644 index 512866c38..000000000 --- a/ui/app/pages/permissions-connect/permissions-connect-header/permissions-connect-header.component.js +++ /dev/null @@ -1,25 +0,0 @@ -import PropTypes from 'prop-types' -import React, { Component } from 'react' -import MetaFoxLogo from '../../../components/ui/metafox-logo' -import { DEFAULT_ROUTE } from '../../../helpers/constants/routes' - -export default class PermissionsConnectHeader extends Component { - static propTypes = { - page: PropTypes.number.isRequired, - } - - render () { - const { page } = this.props - return ( -
    - history.push(DEFAULT_ROUTE)} - /> -
    - { `${page}/2` } -
    -
    - ) - } -} diff --git a/ui/app/pages/permissions-connect/permissions-connect.component.js b/ui/app/pages/permissions-connect/permissions-connect.component.js deleted file mode 100644 index 69dcbd390..000000000 --- a/ui/app/pages/permissions-connect/permissions-connect.component.js +++ /dev/null @@ -1,208 +0,0 @@ -import PropTypes from 'prop-types' -import React, { Component } from 'react' -import PermissionsConnectHeader from './permissions-connect-header' -import PermissionsConnectFooter from './permissions-connect-footer' -import ChooseAccount from './choose-account' -import { getEnvironmentType } from '../../../../app/scripts/lib/util' -import { - ENVIRONMENT_TYPE_FULLSCREEN, - ENVIRONMENT_TYPE_NOTIFICATION, - ENVIRONMENT_TYPE_POPUP, -} from '../../../../app/scripts/lib/enums' -import { DEFAULT_ROUTE, CONNECTED_ROUTE } from '../../helpers/constants/routes' -import PermissionPageContainer from '../../components/app/permission-page-container' - -export default class PermissionConnect extends Component { - static propTypes = { - approvePermissionsRequest: PropTypes.func.isRequired, - rejectPermissionsRequest: PropTypes.func.isRequired, - getRequestAccountTabIds: PropTypes.func.isRequired, - getCurrentWindowTab: PropTypes.func.isRequired, - accounts: PropTypes.array.isRequired, - originName: PropTypes.string, - showNewAccountModal: PropTypes.func.isRequired, - newAccountNumber: PropTypes.number.isRequired, - nativeCurrency: PropTypes.string, - permissionsRequest: PropTypes.object, - addressLastConnectedMap: PropTypes.object, - requestAccountTabs: PropTypes.object, - permissionsRequestId: PropTypes.string, - domains: PropTypes.object, - history: PropTypes.object.isRequired, - } - - static defaultProps = { - originName: '', - nativeCurrency: '', - permissionsRequest: undefined, - addressLastConnectedMap: {}, - requestAccountTabs: {}, - permissionsRequestId: '', - domains: {}, - } - - static contextTypes = { - t: PropTypes.func, - } - - state = { - page: 1, - selectedAccountAddress: '', - permissionAccepted: null, - originName: this.props.originName, - } - - beforeUnload = () => { - const { permissionsRequestId, rejectPermissionsRequest } = this.props - const { permissionAccepted } = this.state - - if (permissionAccepted === null && permissionsRequestId) { - rejectPermissionsRequest(permissionsRequestId) - } - } - - removeBeforeUnload = () => { - if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_FULLSCREEN) { - window.removeEventListener('beforeunload', this.beforeUnload) - } - } - - componentDidUpdate (prevProps) { - const { domains, permissionsRequestId } = this.props - const { originName, page } = this.state - - if (!permissionsRequestId && prevProps.permissionsRequestId && page !== null) { - const permissionDataForDomain = domains && domains[originName] || {} - const permissionsForDomain = permissionDataForDomain.permissions || [] - const prevPermissionDataForDomain = prevProps.domains && prevProps.domains[originName] || {} - const prevPermissionsForDomain = prevPermissionDataForDomain.permissions || [] - const addedAPermission = permissionsForDomain.length > prevPermissionsForDomain.length - if (addedAPermission) { - this.redirectFlow(true) - } else { - this.redirectFlow(false) - } - } else if (permissionsRequestId && prevProps.permissionsRequestId && - permissionsRequestId !== prevProps.permissionsRequestId && page !== null) { - this.setState({ - originName: this.props.originName, - page: 1, - }) - } - } - - selectAccount = (address) => { - this.setState({ - page: 2, - selectedAccountAddress: address, - }) - } - - redirectFlow (accepted) { - const { requestAccountTabs, history } = this.props - const { originName } = this.state - - this.setState({ - page: null, - permissionAccepted: accepted, - }) - this.removeBeforeUnload() - - if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_FULLSCREEN) { - setTimeout(async () => { - const currentTab = await global.platform.currentTab() - try { - if (currentTab.active) { - await global.platform.switchToTab(requestAccountTabs[originName]) - } - } finally { - global.platform.closeTab(currentTab.id) - } - }, 2000) - } else if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION) { - history.push(DEFAULT_ROUTE) - } else if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) { - history.push(CONNECTED_ROUTE) - } - } - - componentDidMount () { - const { - getCurrentWindowTab, - getRequestAccountTabIds, - } = this.props - getCurrentWindowTab() - getRequestAccountTabIds() - - if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_FULLSCREEN) { - window.addEventListener('beforeunload', this.beforeUnload) - } - } - - render () { - const { - approvePermissionsRequest, - rejectPermissionsRequest, - accounts, - showNewAccountModal, - newAccountNumber, - nativeCurrency, - permissionsRequest, - addressLastConnectedMap, - permissionsRequestId, - } = this.props - const { page, selectedAccountAddress, permissionAccepted, originName } = this.state - - return ( -
    - { page !== null - ? - : null - } - { page === 1 - ? ( - this.selectAccount(address)} - selectNewAccountViaModal={() => { - showNewAccountModal({ - onCreateNewAccount: this.selectAccount, - newAccountNumber, - }) - }} - addressLastConnectedMap={addressLastConnectedMap} - cancelPermissionsRequest={requestId => { - if (requestId) { - rejectPermissionsRequest(requestId) - this.redirectFlow(false) - } - }} - permissionsRequestId={permissionsRequestId} - /> - ) - : ( -
    - { - approvePermissionsRequest(requestId, accounts) - this.redirectFlow(true) - }} - rejectPermissionsRequest={requestId => { - rejectPermissionsRequest(requestId) - this.redirectFlow(false) - }} - selectedIdentity={accounts.find(account => account.address === selectedAccountAddress)} - redirect={page === null} - permissionRejected={ permissionAccepted === false } - /> - -
    - ) - } -
    - ) - } -} diff --git a/ui/app/pages/permissions-connect/permissions-connect.container.js b/ui/app/pages/permissions-connect/permissions-connect.container.js deleted file mode 100644 index 1209bd9cc..000000000 --- a/ui/app/pages/permissions-connect/permissions-connect.container.js +++ /dev/null @@ -1,66 +0,0 @@ -import { connect } from 'react-redux' -import { compose } from 'recompose' -import { withRouter } from 'react-router-dom' -import PermissionApproval from './permissions-connect.component' -import { - getFirstPermissionRequest, - getNativeCurrency, - getAccountsWithLabels, - getLastConnectedInfo, - getPermissionsDomains, -} from '../../selectors/selectors' -import { formatDate } from '../../helpers/utils/util' -import { approvePermissionsRequest, rejectPermissionsRequest, showModal, getCurrentWindowTab, getRequestAccountTabIds } from '../../store/actions' - -const mapStateToProps = state => { - const permissionsRequest = getFirstPermissionRequest(state) - const { metadata = {} } = permissionsRequest || {} - const { origin } = metadata - const nativeCurrency = getNativeCurrency(state) - - const accountsWithLabels = getAccountsWithLabels(state) - - const { requestAccountTabs = {} } = state.appState - - const lastConnectedInfo = getLastConnectedInfo(state) || {} - const addressLastConnectedMap = lastConnectedInfo[origin] || {} - - Object.keys(addressLastConnectedMap).forEach(key => { - addressLastConnectedMap[key] = formatDate(addressLastConnectedMap[key], 'yyyy-M-d') - }) - - const permissionsRequestId = (permissionsRequest && permissionsRequest.metadata) ? permissionsRequest.metadata.id : null - - return { - permissionsRequest, - permissionsRequestId, - accounts: accountsWithLabels, - originName: origin, - newAccountNumber: accountsWithLabels.length + 1, - nativeCurrency, - requestAccountTabs, - addressLastConnectedMap, - domains: getPermissionsDomains(state), - } -} - -const mapDispatchToProps = dispatch => { - return { - approvePermissionsRequest: (requestId, accounts) => dispatch(approvePermissionsRequest(requestId, accounts)), - rejectPermissionsRequest: requestId => dispatch(rejectPermissionsRequest(requestId)), - showNewAccountModal: ({ onCreateNewAccount, newAccountNumber }) => { - return dispatch(showModal({ - name: 'NEW_ACCOUNT', - onCreateNewAccount, - newAccountNumber, - })) - }, - getRequestAccountTabIds: () => dispatch(getRequestAccountTabIds()), - getCurrentWindowTab: () => dispatch(getCurrentWindowTab()), - } -} - -export default compose( - withRouter, - connect(mapStateToProps, mapDispatchToProps) -)(PermissionApproval) diff --git a/ui/app/pages/provider-approval/index.js b/ui/app/pages/provider-approval/index.js new file mode 100644 index 000000000..4162f3155 --- /dev/null +++ b/ui/app/pages/provider-approval/index.js @@ -0,0 +1 @@ +export { default } from './provider-approval.container' diff --git a/ui/app/pages/provider-approval/provider-approval.component.js b/ui/app/pages/provider-approval/provider-approval.component.js new file mode 100644 index 000000000..8532fe60d --- /dev/null +++ b/ui/app/pages/provider-approval/provider-approval.component.js @@ -0,0 +1,36 @@ +import PropTypes from 'prop-types' +import React, { Component } from 'react' +import ProviderPageContainer from '../../components/app/provider-page-container' + +export default class ProviderApproval extends Component { + static propTypes = { + approveProviderRequestByOrigin: PropTypes.func.isRequired, + rejectProviderRequestByOrigin: PropTypes.func.isRequired, + providerRequest: PropTypes.exact({ + hostname: PropTypes.string.isRequired, + siteImage: PropTypes.string, + siteTitle: PropTypes.string, + origin: PropTypes.string.isRequired, + extensionId: PropTypes.string, + }).isRequired, + }; + + static contextTypes = { + t: PropTypes.func, + }; + + render () { + const { approveProviderRequestByOrigin, providerRequest, rejectProviderRequestByOrigin } = this.props + return ( + + ) + } +} diff --git a/ui/app/pages/provider-approval/provider-approval.container.js b/ui/app/pages/provider-approval/provider-approval.container.js new file mode 100644 index 000000000..1e167ddb7 --- /dev/null +++ b/ui/app/pages/provider-approval/provider-approval.container.js @@ -0,0 +1,12 @@ +import { connect } from 'react-redux' +import ProviderApproval from './provider-approval.component' +import { approveProviderRequestByOrigin, rejectProviderRequestByOrigin } from '../../store/actions' + +function mapDispatchToProps (dispatch) { + return { + approveProviderRequestByOrigin: origin => dispatch(approveProviderRequestByOrigin(origin)), + rejectProviderRequestByOrigin: origin => dispatch(rejectProviderRequestByOrigin(origin)), + } +} + +export default connect(null, mapDispatchToProps)(ProviderApproval) diff --git a/ui/app/pages/routes/index.js b/ui/app/pages/routes/index.js index b2a2ced68..01e61b1b4 100644 --- a/ui/app/pages/routes/index.js +++ b/ui/app/pages/routes/index.js @@ -6,12 +6,7 @@ import { compose } from 'recompose' import actions from '../../store/actions' import log from 'loglevel' import IdleTimer from 'react-idle-timer' -import { - getNetworkIdentifier, - preferencesSelector, - hasPermissionRequests, - getAddressConnectedToCurrentTab, -} from '../../selectors/selectors' +import {getNetworkIdentifier, preferencesSelector} from '../../selectors/selectors' import classnames from 'classnames' // init @@ -30,11 +25,9 @@ import Settings from '../settings' import Authenticated from '../../helpers/higher-order-components/authenticated' import Initialized from '../../helpers/higher-order-components/initialized' import Lock from '../lock' -import PermissionsConnect from '../permissions-connect' -import ConnectedSites from '../connected-sites' const RestoreVaultPage = require('../keychains/restore-vault').default const RevealSeedConfirmation = require('../keychains/reveal-seed') -const MobileSyncPage = require('../mobile-sync').default +const MobileSyncPage = require('../mobile-sync') const AddTokenPage = require('../add-token') const ConfirmAddTokenPage = require('../confirm-add-token') const ConfirmAddSuggestedTokenPage = require('../confirm-add-suggested-token') @@ -74,8 +67,6 @@ import { CONFIRM_TRANSACTION_ROUTE, INITIALIZE_ROUTE, INITIALIZE_UNLOCK_ROUTE, - CONNECT_ROUTE, - CONNECTED_ROUTE, } from '../../helpers/constants/routes' // enums @@ -107,20 +98,6 @@ class Routes extends Component { }) } - componentDidMount () { - const { addressConnectedToCurrentTab, showAccountDetail, selectedAddress } = this.props - if (addressConnectedToCurrentTab && addressConnectedToCurrentTab !== selectedAddress) { - showAccountDetail(addressConnectedToCurrentTab) - } - } - - componentDidUpdate (prevProps) { - const { addressConnectedToCurrentTab, showAccountDetail } = this.props - if (addressConnectedToCurrentTab && addressConnectedToCurrentTab !== prevProps.addressConnectedToCurrentTab) { - showAccountDetail(addressConnectedToCurrentTab) - } - } - renderRoutes () { const { autoLogoutTimeLimit, setLastActiveTime } = this.props @@ -139,8 +116,6 @@ class Routes extends Component { - - ) @@ -166,8 +141,13 @@ class Routes extends Component { return Boolean(matchPath(location.pathname, { path: CONFIRM_TRANSACTION_ROUTE, exact: false })) } + hasProviderRequests () { + const { providerRequests } = this.props + return Array.isArray(providerRequests) && providerRequests.length > 0 + } + hideAppHeader () { - const { location, hasPermissionsRequests } = this.props + const { location } = this.props const isInitializing = Boolean(matchPath(location.pathname, { path: INITIALIZE_ROUTE, exact: false, @@ -182,15 +162,7 @@ class Routes extends Component { } if (window.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_POPUP) { - return this.onConfirmPage() || hasPermissionsRequests - } - - const isHandlingPermissionsRequest = Boolean(matchPath(location.pathname, { - path: CONNECT_ROUTE, exact: false, - })) - - if (hasPermissionsRequests || isHandlingPermissionsRequest) { - return true + return this.onConfirmPage() || this.hasProviderRequests() } } @@ -288,10 +260,8 @@ class Routes extends Component { toggleMetamaskActive () { if (!this.props.isUnlocked) { // currently inactive: redirect to password box - const passwordBox = document.querySelector('input[type=password]') - if (!passwordBox) { - return - } + var passwordBox = document.querySelector('input[type=password]') + if (!passwordBox) return passwordBox.focus() } else { // currently active: deactivate @@ -362,7 +332,6 @@ Routes.propTypes = { textDirection: PropTypes.string, network: PropTypes.string, provider: PropTypes.object, - selectedAddress: PropTypes.string, frequentRpcListDetail: PropTypes.array, currentView: PropTypes.object, sidebar: PropTypes.object, @@ -377,18 +346,12 @@ Routes.propTypes = { isMouseUser: PropTypes.bool, setMouseUserState: PropTypes.func, providerId: PropTypes.string, - hasPermissionsRequests: PropTypes.bool, + providerRequests: PropTypes.array, autoLogoutTimeLimit: PropTypes.number, - addressConnectedToCurrentTab: PropTypes.string, - showAccountDetail: PropTypes.func, -} - -Routes.defaultProps = { - selectedAddress: undefined, } function mapStateToProps (state) { - const { appState } = state + const { appState, metamask } = state const { sidebar, alertOpen, @@ -412,14 +375,12 @@ function mapStateToProps (state) { submittedPendingTransactions: submittedPendingTransactionsSelector(state), network: state.metamask.network, provider: state.metamask.provider, - selectedAddress: state.metamask.selectedAddress, frequentRpcListDetail: state.metamask.frequentRpcListDetail || [], currentCurrency: state.metamask.currentCurrency, isMouseUser: state.appState.isMouseUser, providerId: getNetworkIdentifier(state), autoLogoutTimeLimit, - hasPermissionsRequests: hasPermissionRequests(state), - addressConnectedToCurrentTab: getAddressConnectedToCurrentTab(state), + providerRequests: metamask.providerRequests, } } @@ -430,7 +391,6 @@ function mapDispatchToProps (dispatch) { setCurrentCurrencyToUSD: () => dispatch(actions.setCurrentCurrency('usd')), setMouseUserState: (isMouseUser) => dispatch(actions.setMouseUserState(isMouseUser)), setLastActiveTime: () => dispatch(actions.setLastActiveTime()), - showAccountDetail: address => dispatch(actions.showAccountDetail(address)), } } diff --git a/ui/app/pages/send/account-list-item/account-list-item.component.js b/ui/app/pages/send/account-list-item/account-list-item.component.js index 8c676f419..20ced2f09 100644 --- a/ui/app/pages/send/account-list-item/account-list-item.component.js +++ b/ui/app/pages/send/account-list-item/account-list-item.component.js @@ -44,32 +44,30 @@ export default class AccountListItem extends Component { const { name, address, balance } = account || {} - return ( -
    handleClick && handleClick({ name, address, balance })} - > + return (
    handleClick && handleClick({ name, address, balance })} + > -
    - +
    + -
    { name || address }
    +
    { name || address }
    - {icon &&
    { icon }
    } + {icon &&
    { icon }
    } -
    +
    - {displayAddress && name && ( -
    - { checksumAddress(address) } -
    - )} + {displayAddress && name &&
    + { checksumAddress(address) } +
    } - {displayBalance && ( + { + displayBalance && ( -
    +
    { - balanceIsCached - ? * - : null + balanceIsCached ? * : null }
    - {showFiat && ( - - )} + { + showFiat && ( + + ) + }
    - )} + ) + } -
    - ) +
    ) } } diff --git a/ui/app/pages/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 e54a8c493..5a2bd5638 100644 --- a/ui/app/pages/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 @@ -23,19 +23,17 @@ describe('AccountListItem Component', function () { let wrapper beforeEach(() => { - wrapper = shallow(( - } - /> - ), { context: { t: str => str + '_t' } }) + wrapper = shallow(} + />, { context: { t: str => str + '_t' } }) }) afterEach(() => { diff --git a/ui/app/pages/send/send-content/add-recipient/add-recipient.component.js b/ui/app/pages/send/send-content/add-recipient/add-recipient.component.js index a2e6b49db..e5edbc08d 100644 --- a/ui/app/pages/send/send-content/add-recipient/add-recipient.component.js +++ b/ui/app/pages/send/send-content/add-recipient/add-recipient.component.js @@ -64,6 +64,7 @@ export default class AddRecipient extends Component { state = { isShowingTransfer: false, + isShowingAllRecent: false, } selectRecipient = (to, nickname = '') => { @@ -155,7 +156,7 @@ export default class AddRecipient extends Component { className="send__select-recipient-wrapper__list__link" onClick={() => this.setState({ isShowingTransfer: false })} > -
    +
    { t('backToAll') }
    { - if (address === ZERO_ADDRESS) { - throw new Error(this.context.t('noAddressForName')) - } - if (address === ZERO_X_ERROR_ADDRESS) { - throw new Error(this.context.t('ensRegistrationError')) - } + if (address === ZERO_ADDRESS) throw new Error(this.context.t('noAddressForName')) + if (address === ZERO_X_ERROR_ADDRESS) throw new Error(this.context.t('ensRegistrationError')) this.props.updateEnsResolution(address) }) .catch((reason) => { @@ -233,11 +230,9 @@ export default class EnsInput extends Component { } ensIconContents () { - const { loadingEns, ensFailure, ensResolution, toError } = this.state + const { loadingEns, ensFailure, ensResolution, toError } = this.state || { ensResolution: ZERO_ADDRESS } - if (toError) { - return - } + if (toError) return if (loadingEns) { return ( diff --git a/ui/app/pages/send/send-content/add-recipient/tests/add-recipient-component.test.js b/ui/app/pages/send/send-content/add-recipient/tests/add-recipient-component.test.js index 115ad5b62..1271c9810 100644 --- a/ui/app/pages/send/send-content/add-recipient/tests/add-recipient-component.test.js +++ b/ui/app/pages/send/send-content/add-recipient/tests/add-recipient-component.test.js @@ -19,25 +19,23 @@ describe('AddRecipient Component', function () { let instance beforeEach(() => { - wrapper = shallow(( - - ), { context: { t: str => str + '_t' } }) + wrapper = shallow(, { context: { t: str => str + '_t' } }) instance = wrapper.instance() }) diff --git a/ui/app/pages/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 48bc32d3a..30ef4c678 100644 --- a/ui/app/pages/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 @@ -18,17 +18,15 @@ describe('AmountMaxButton Component', function () { let instance beforeEach(() => { - wrapper = shallow(( - - ), { + wrapper = shallow(, { context: { t: str => str + '_t', metricsEvent: () => {}, diff --git a/ui/app/pages/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 fe648d4d8..bfc0f7e64 100644 --- a/ui/app/pages/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 @@ -25,25 +25,23 @@ describe('SendAmountRow Component', function () { let instance beforeEach(() => { - wrapper = shallow(( - - ), { context: { t: str => str + '_t' } }) + wrapper = shallow(, { context: { t: str => str + '_t' } }) instance = wrapper.instance() }) 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 index c0b604505..1dcd0bd2c 100644 --- 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 @@ -128,8 +128,7 @@ export default class SendAssetRow extends Component { return (
    this.selectToken(address)} >
    diff --git a/ui/app/pages/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 42b19397b..5f5d26231 100644 --- a/ui/app/pages/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 @@ -17,7 +17,7 @@ export default class SendDropdownList extends Component { getListItemIcon (accountAddress, activeAddress) { return accountAddress === activeAddress - ? + ? : null } @@ -29,28 +29,24 @@ export default class SendDropdownList extends Component { activeAddress, } = this.props - return ( -
    -
    closeDropdown()} - /> -
    - {accounts.map((account, index) => ( - { - onSelect(account) - closeDropdown() - }} - icon={this.getListItemIcon(account.address, activeAddress)} - key={`send-dropdown-account-#${index}`} - /> - ))} -
    + return (
    +
    closeDropdown()} + /> +
    + {accounts.map((account, index) => { + onSelect(account) + closeDropdown() + }} + icon={this.getListItemIcon(account.address, activeAddress)} + key={`send-dropdown-account-#${index}`} + />)}
    - ) +
    ) } } diff --git a/ui/app/pages/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 77efddfd4..bc6c97586 100644 --- a/ui/app/pages/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 @@ -17,18 +17,16 @@ describe('SendDropdownList Component', function () { let wrapper beforeEach(() => { - wrapper = shallow(( - - ), { context: { t: str => str + '_t' } }) + wrapper = shallow(, { context: { t: str => str + '_t' } }) }) afterEach(() => { @@ -41,7 +39,7 @@ describe('SendDropdownList Component', function () { it('should return check icon if the passed addresses are the same', () => { assert.deepEqual( wrapper.instance().getListItemIcon('mockAccount0', 'mockAccount0'), - + ) }) diff --git a/ui/app/pages/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 473dbb0b1..37af59e29 100644 --- a/ui/app/pages/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 @@ -38,16 +38,12 @@ export default class GasFeeDisplay extends Component {
    ) : gasLoadingError - ? ( -
    - {this.context.t('setGasPrice')} -
    - ) - : ( -
    - {this.context.t('loading')} -
    - ) + ?
    + {this.context.t('setGasPrice')} +
    + :
    + {this.context.t('loading')} +
    } +
    +
    +
    + ) + } + + renderApprovedOriginsList () { + const { t } = this.context + const { approvedOrigins, rejectProviderRequestByOrigin, showClearApprovalModal } = this.props + const approvedEntries = Object.entries(approvedOrigins) + const approvalListEmpty = approvedEntries.length === 0 + + return ( +
    +
    + { t('connected') } + + { t('connectedDescription') } + +
    +
    + { + approvalListEmpty + ?
    + : null + } + { + approvedEntries.map(([origin, { siteTitle, siteImage }]) => ( + { + rejectProviderRequestByOrigin(origin) + }} + /> + )) + } +
    +
    + +
    +
    + ) + } + + render () { + return ( +
    + { this.renderNewOriginInput() } + { this.renderApprovedOriginsList() } +
    + ) + } +} diff --git a/ui/app/pages/settings/connections-tab/connections-tab.container.js b/ui/app/pages/settings/connections-tab/connections-tab.container.js new file mode 100644 index 000000000..cf3efc2b4 --- /dev/null +++ b/ui/app/pages/settings/connections-tab/connections-tab.container.js @@ -0,0 +1,39 @@ +import ConnectionsTab from './connections-tab.component' +import { compose } from 'recompose' +import { connect } from 'react-redux' +import { withRouter } from 'react-router-dom' +import { + approveProviderRequestByOrigin, + rejectProviderRequestByOrigin, + showModal, +} from '../../../store/actions' + +export const mapStateToProps = state => { + const { + activeTab, + metamask, + } = state + const { + approvedOrigins, + } = metamask + + return { + activeTab, + approvedOrigins, + } +} + +export const mapDispatchToProps = dispatch => { + return { + approveProviderRequestByOrigin: (origin) => dispatch(approveProviderRequestByOrigin(origin)), + rejectProviderRequestByOrigin: (origin) => dispatch(rejectProviderRequestByOrigin(origin)), + showClearApprovalModal: () => dispatch(showModal({ + name: 'CLEAR_APPROVED_ORIGINS', + })), + } +} + +export default compose( + withRouter, + connect(mapStateToProps, mapDispatchToProps) +)(ConnectionsTab) diff --git a/ui/app/pages/settings/connections-tab/index.js b/ui/app/pages/settings/connections-tab/index.js new file mode 100644 index 000000000..b04f4e33a --- /dev/null +++ b/ui/app/pages/settings/connections-tab/index.js @@ -0,0 +1 @@ +export { default } from './connections-tab.container' diff --git a/ui/app/pages/settings/connections-tab/index.scss b/ui/app/pages/settings/connections-tab/index.scss new file mode 100644 index 000000000..249a7193f --- /dev/null +++ b/ui/app/pages/settings/connections-tab/index.scss @@ -0,0 +1 @@ +@import './connected-site-row/index'; diff --git a/ui/app/pages/settings/contact-list-tab/add-contact/add-contact.component.js b/ui/app/pages/settings/contact-list-tab/add-contact/add-contact.component.js index 60f097d79..56d4415cf 100644 --- a/ui/app/pages/settings/contact-list-tab/add-contact/add-contact.component.js +++ b/ui/app/pages/settings/contact-list-tab/add-contact/add-contact.component.js @@ -23,7 +23,7 @@ export default class AddContact extends PureComponent { } state = { - newName: '', + nickname: '', ethAddress: '', ensAddress: '', error: '', @@ -63,9 +63,7 @@ export default class AddContact extends PureComponent { return ( { - this.props.scanQrCode() - }} + scanQrCode={_ => { this.props.scanQrCode() }} onChange={this.dValidate} onPaste={text => this.setState({ ethAddress: text })} onReset={() => this.setState({ ethAddress: '', ensAddress: '' })} @@ -85,14 +83,12 @@ export default class AddContact extends PureComponent { return (
    - {this.state.ensAddress && ( -
    - -
    - { this.state.ensAddress } -
    + {this.state.ensAddress &&
    + +
    + { this.state.ensAddress }
    - )} +
    }
    diff --git a/ui/app/pages/settings/contact-list-tab/contact-list-tab.component.js b/ui/app/pages/settings/contact-list-tab/contact-list-tab.component.js index 835963a83..f7a01d672 100644 --- a/ui/app/pages/settings/contact-list-tab/contact-list-tab.component.js +++ b/ui/app/pages/settings/contact-list-tab/contact-list-tab.component.js @@ -49,19 +49,16 @@ export default class ContactListTab extends Component { renderAddButton () { const { history } = this.props - return ( -
    { - history.push(CONTACT_ADD_ROUTE) - }} - > - -
    - ) + return
    { + history.push(CONTACT_ADD_ROUTE) + }}> + +
    } renderMyAccountsButton () { @@ -101,23 +98,19 @@ export default class ContactListTab extends Component { ContactContentComponent = AddContact } - return (ContactContentComponent && ( -
    - -
    - )) + return (ContactContentComponent &&
    + +
    ) } renderAddressBookContent () { const { hideAddressBook, showingMyAccounts } = this.props if (!hideAddressBook && !showingMyAccounts) { - return ( -
    - { this.renderMyAccountsButton() } - { this.renderAddresses() } -
    - ) + return (
    + { this.renderMyAccountsButton() } + { this.renderAddresses() } +
    ) } else if (!hideAddressBook && showingMyAccounts) { return () } @@ -130,11 +123,9 @@ export default class ContactListTab extends Component {
    { this.renderAddressBookContent() } { this.renderContactContent() } - {!addingContact && ( -
    - { this.renderAddButton() } -
    - )} + {!addingContact &&
    + { this.renderAddButton() } +
    }
    ) } diff --git a/ui/app/pages/settings/contact-list-tab/edit-contact/edit-contact.component.js b/ui/app/pages/settings/contact-list-tab/edit-contact/edit-contact.component.js index 88b14f5e3..9373cbe6b 100644 --- a/ui/app/pages/settings/contact-list-tab/edit-contact/edit-contact.component.js +++ b/ui/app/pages/settings/contact-list-tab/edit-contact/edit-contact.component.js @@ -45,7 +45,7 @@ export default class EditContact extends PureComponent { return (
    - + -
    - ) + ?
    + +
    : null }
    diff --git a/ui/app/pages/settings/settings.component.js b/ui/app/pages/settings/settings.component.js index 12d395f31..975ab4e35 100644 --- a/ui/app/pages/settings/settings.component.js +++ b/ui/app/pages/settings/settings.component.js @@ -4,6 +4,7 @@ import { Switch, Route, matchPath, withRouter } from 'react-router-dom' import TabBar from '../../components/app/tab-bar' import c from 'classnames' import SettingsTab from './settings-tab' +import ConnectionsTab from './connections-tab' import NetworksTab from './networks-tab' import AdvancedTab from './advanced-tab' import InfoTab from './info-tab' @@ -14,6 +15,7 @@ import { ADVANCED_ROUTE, SECURITY_ROUTE, GENERAL_ROUTE, + CONNECTIONS_ROUTE, ABOUT_US_ROUTE, SETTINGS_ROUTE, NETWORKS_ROUTE, @@ -133,19 +135,13 @@ class SettingsPage extends PureComponent {
    initialBreadCrumbRoute && history.push(initialBreadCrumbRoute)} - > - {subheaderText} -
    - {breadCrumbTextKey && ( -
    - {' > '}{t(breadCrumbTextKey)} -
    - )} - {isAddressEntryPage && ( -
    - {' > '}{addressName} -
    - )} + >{subheaderText}
    + {breadCrumbTextKey &&
    {' > '}{t(breadCrumbTextKey)}
    } + {isAddressEntryPage &&
    {' > '}{addressName}
    }
    ) } @@ -158,6 +154,7 @@ class SettingsPage extends PureComponent { + { - const { address, name, balance } = account - return { - address, - truncatedAddress: `${address.slice(0, 6)}...${address.slice(-4)}`, - addressLabel: `${name} (...${address.slice(address.length - 4)})`, - label: name, - balance, - } - }) - return accountsWithLabels -} - function getCurrentAccountWithSendEtherInfo (state) { const currentAddress = getSelectedAddress(state) const accounts = accountsWithSendEtherInfoSelector(state) @@ -385,22 +353,6 @@ function getCustomNonceValue (state) { return String(state.metamask.customNonceValue) } -function getPermissionsDescriptions (state) { - return state.metamask.permissionsDescriptions -} - -function getPermissionsRequests (state) { - return state.metamask.permissionsRequests -} - -function getDomainMetadata (state) { - return state.metamask.domainMetadata -} - -function getActiveTab (state) { - return state.activeTab -} - function getMetaMetricState (state) { return { network: getCurrentNetworkId(state), @@ -434,144 +386,3 @@ function getKnownMethodData (state, data) { function getFeatureFlags (state) { return state.metamask.featureFlags } - -function getFirstPermissionRequest (state) { - const requests = getPermissionsRequests(state) - return requests && requests[0] ? requests[0] : null -} - -function hasPermissionRequests (state) { - return Boolean(getFirstPermissionRequest(state)) -} - -function getPermissionsDomains (state) { - return state.metamask.domains -} - -function getAddressConnectedDomainMap (state) { - const { - domains, - domainMetadata, - } = state.metamask - - const addressConnectedIconMap = {} - - if (domains) { - Object.keys(domains).forEach(domainKey => { - const { permissions } = domains[domainKey] - const { icon, name } = domainMetadata[domainKey] || {} - permissions.forEach(perm => { - const caveats = perm.caveats || [] - const exposedAccountCaveat = caveats.find(caveat => caveat.name === 'exposedAccounts') - if (exposedAccountCaveat && exposedAccountCaveat.value && exposedAccountCaveat.value.length) { - exposedAccountCaveat.value.forEach(address => { - const nameToRender = name || domainKey - addressConnectedIconMap[address] = addressConnectedIconMap[address] - ? { ...addressConnectedIconMap[address], [domainKey]: { icon, name: nameToRender } } - : { [domainKey]: { icon, name: nameToRender } } - }) - } - }) - }) - } - - return addressConnectedIconMap -} - -function getDomainToConnectedAddressMap (state) { - const { domains = {} } = state.metamask - - const domainToConnectedAddressMap = mapObjectValues(domains, (_, { permissions }) => { - const ethAccountsPermissions = permissions.filter(permission => permission.parentCapability === 'eth_accounts') - const ethAccountsPermissionsExposedAccountAddresses = ethAccountsPermissions.map(permission => { - const caveats = permission.caveats - const exposedAccountsCaveats = caveats.filter(caveat => caveat.name === 'exposedAccounts') - const exposedAccountsAddresses = exposedAccountsCaveats.map(caveat => caveat.value[0]) - return exposedAccountsAddresses - }) - const allAddressesConnectedToDomain = ethAccountsPermissionsExposedAccountAddresses.reduce((acc, arrayOfAddresses) => { - return [ ...acc, ...arrayOfAddresses ] - }, []) - return allAddressesConnectedToDomain - }) - - return domainToConnectedAddressMap -} - -function getAddressConnectedToCurrentTab (state) { - const domainToConnectedAddressMap = getDomainToConnectedAddressMap(state) - const originOfCurrentTab = getOriginOfCurrentTab(state) - const addressesConnectedToCurrentTab = domainToConnectedAddressMap[originOfCurrentTab] - const addressConnectedToCurrentTab = addressesConnectedToCurrentTab && addressesConnectedToCurrentTab[0] - return addressConnectedToCurrentTab -} - -function getRenderablePermissionsDomains (state) { - const { - domains = {}, - domainMetadata, - permissionsHistory, - permissionsDescriptions, - selectedAddress, - } = state.metamask - - const renderableDomains = Object.keys(domains).reduce((acc, domainKey) => { - const { permissions } = domains[domainKey] - const permissionsWithCaveatsForSelectedAddress = permissions.filter(perm => { - const caveats = perm.caveats || [] - const exposedAccountCaveat = caveats.find(caveat => caveat.name === 'exposedAccounts') - const exposedAccountCaveatValue = exposedAccountCaveat && exposedAccountCaveat.value && exposedAccountCaveat.value.length - ? exposedAccountCaveat.value[0] - : {} - return exposedAccountCaveatValue === selectedAddress - }) - - if (permissionsWithCaveatsForSelectedAddress.length) { - const permissionKeys = permissions.map(permission => permission.parentCapability) - const { - name, - icon, - extensionId, - } = domainMetadata[domainKey] || {} - const permissionsHistoryForDomain = permissionsHistory[domainKey] || {} - const ethAccountsPermissionsForDomain = permissionsHistoryForDomain['eth_accounts'] || {} - const accountsLastConnectedTime = ethAccountsPermissionsForDomain.accounts || {} - const selectedAddressLastConnectedTime = accountsLastConnectedTime[selectedAddress] - - const lastConnectedTime = selectedAddressLastConnectedTime - ? formatDate(selectedAddressLastConnectedTime, 'yyyy-M-d') - : '' - - return [ ...acc, { - name: name || domainKey, - secondaryName: name ? domainKey : '', - icon, - key: domainKey, - lastConnectedTime, - permissionDescriptions: permissionKeys.map(permissionKey => permissionsDescriptions[permissionKey]), - extensionId, - }] - } else { - return acc - } - }, []) - - return renderableDomains -} - -function getOriginOfCurrentTab (state) { - const { activeTab } = state - return activeTab && activeTab.url && getOriginFromUrl(activeTab.url) -} - -function getLastConnectedInfo (state) { - const { permissionsHistory = {} } = state.metamask - const lastConnectedInfoData = Object.keys(permissionsHistory).reduce((acc, origin) => { - const ethAccountsHistory = JSON.parse(JSON.stringify(permissionsHistory[origin]['eth_accounts'])) - return { - ...acc, - [origin]: ethAccountsHistory.accounts, - } - }, {}) - return lastConnectedInfoData -} diff --git a/ui/app/store/actions.js b/ui/app/store/actions.js index a09548f0f..2642e89e9 100644 --- a/ui/app/store/actions.js +++ b/ui/app/store/actions.js @@ -17,7 +17,7 @@ const { hasUnconfirmedTransactions } = require('../helpers/utils/confirm-tx.util const gasDuck = require('../ducks/gas/gas.duck') const WebcamUtils = require('../../lib/webcam-utils') -const actions = { +var actions = { _setBackgroundConnection: _setBackgroundConnection, GO_HOME: 'GO_HOME', @@ -328,6 +328,7 @@ const actions = { setUseNativeCurrencyAsPrimaryCurrencyPreference, setShowFiatConversionOnTestnetsPreference, setAutoLogoutTimeLimit, + unsetMigratedPrivacyMode, // Onboarding setCompletedOnboarding, @@ -351,12 +352,9 @@ const actions = { createSpeedUpTransaction, createRetryTransaction, - // Permissions - approvePermissionsRequest, - clearPermissions, - rejectPermissionsRequest, - removePermissionsFor, - legacyExposeAccounts, + approveProviderRequestByOrigin, + rejectProviderRequestByOrigin, + clearApprovedOrigins, setFirstTimeFlowType, SET_FIRST_TIME_FLOW_TYPE: 'SET_FIRST_TIME_FLOW_TYPE', @@ -397,18 +395,11 @@ const actions = { turnThreeBoxSyncingOnAndInitialize, tryReverseResolveAddress, - - getRequestAccountTabIds, - getCurrentWindowTab, - SET_REQUEST_ACCOUNT_TABS: 'SET_REQUEST_ACCOUNT_TABS', - SET_CURRENT_WINDOW_TAB: 'SET_CURRENT_WINDOW_TAB', - getOpenMetamaskTabsIds, - SET_OPEN_METAMASK_TAB_IDS: 'SET_OPEN_METAMASK_TAB_IDS', } module.exports = actions -let background = null +var background = null function _setBackgroundConnection (backgroundConnection) { background = backgroundConnection } @@ -685,9 +676,7 @@ function addNewKeyring (type, opts) { log.debug(`background.addNewKeyring`) background.addNewKeyring(type, opts, (err) => { dispatch(actions.hideLoadingIndication()) - if (err) { - return dispatch(actions.displayWarning(err.message)) - } + if (err) return dispatch(actions.displayWarning(err.message)) dispatch(actions.showAccountsPage()) }) } @@ -2180,7 +2169,7 @@ function requestExportAccount () { } function exportAccount (password, address) { - const self = this + var self = this return function (dispatch) { dispatch(self.showLoadingIndication()) @@ -2300,9 +2289,7 @@ function pairUpdate (coin) { dispatch(actions.hideWarning()) shapeShiftRequest('marketinfo', {pair: `${coin.toLowerCase()}_eth`}, (mktResponse) => { dispatch(actions.hideSubLoadingIndication()) - if (mktResponse.error) { - return dispatch(actions.displayWarning(mktResponse.error)) - } + if (mktResponse.error) return dispatch(actions.displayWarning(mktResponse.error)) dispatch({ type: actions.PAIR_UPDATE, value: { @@ -2314,15 +2301,13 @@ function pairUpdate (coin) { } function shapeShiftSubview () { - const pair = 'btc_eth' + var pair = 'btc_eth' return (dispatch) => { dispatch(actions.showSubLoadingIndication()) shapeShiftRequest('marketinfo', {pair}, (mktResponse) => { shapeShiftRequest('getcoins', {}, (response) => { dispatch(actions.hideSubLoadingIndication()) - if (mktResponse.error) { - return dispatch(actions.displayWarning(mktResponse.error)) - } + if (mktResponse.error) return dispatch(actions.displayWarning(mktResponse.error)) dispatch({ type: actions.SHAPESHIFT_SUBVIEW, value: { @@ -2340,10 +2325,8 @@ function coinShiftRquest (data, marketData) { dispatch(actions.showLoadingIndication()) shapeShiftRequest('shift', { method: 'POST', data}, (response) => { dispatch(actions.hideLoadingIndication()) - if (response.error) { - return dispatch(actions.displayWarning(response.error)) - } - const message = ` + if (response.error) return dispatch(actions.displayWarning(response.error)) + var message = ` Deposit your ${response.depositType} to the address below:` log.debug(`background.createShapeShiftTx`) background.createShapeShiftTx(response.deposit, response.depositType) @@ -2377,11 +2360,9 @@ function reshowQrCode (data, coin) { return (dispatch) => { dispatch(actions.showLoadingIndication()) shapeShiftRequest('marketinfo', {pair: `${coin.toLowerCase()}_eth`}, (mktResponse) => { - if (mktResponse.error) { - return dispatch(actions.displayWarning(mktResponse.error)) - } + if (mktResponse.error) return dispatch(actions.displayWarning(mktResponse.error)) - const message = [ + var message = [ `Deposit your ${coin} to the address below:`, `Deposit Limit: ${mktResponse.limit}`, `Deposit Minimum:${mktResponse.minimum}`, @@ -2394,10 +2375,10 @@ function reshowQrCode (data, coin) { } function shapeShiftRequest (query, options = {}, cb) { - let queryResponse, method + var queryResponse, method options.method ? method = options.method : method = 'GET' - const requestListner = function () { + var requestListner = function () { try { queryResponse = JSON.parse(this.responseText) if (cb) { @@ -2412,12 +2393,12 @@ function shapeShiftRequest (query, options = {}, cb) { } } - const shapShiftReq = new XMLHttpRequest() + var shapShiftReq = new XMLHttpRequest() shapShiftReq.addEventListener('load', requestListner) shapShiftReq.open(method, `https://shapeshift.io/${query}/${options.pair ? options.pair : ''}`, true) if (options.method === 'POST') { - const jsonObj = JSON.stringify(options.data) + var jsonObj = JSON.stringify(options.data) shapShiftReq.setRequestHeader('Content-Type', 'application/json') return shapShiftReq.send(jsonObj) } else { @@ -2718,59 +2699,24 @@ function setPendingTokens (pendingTokens) { } } -// Permissions - -/** - * Approves the permission requests with the given IDs. - * @param {string} requestId - The id of the permissions request. - * @param {string[]} accounts - The accounts to expose, if any. - */ -function approvePermissionsRequest (requestId, accounts) { +function approveProviderRequestByOrigin (origin) { return () => { - background.approvePermissionsRequest(requestId, accounts) + background.approveProviderRequestByOrigin(origin) } } -/** - * Rejects the permission requests with the given IDs. - * @param {Array} requestId - */ -function rejectPermissionsRequest (requestId) { +function rejectProviderRequestByOrigin (origin) { return () => { - background.rejectPermissionsRequest(requestId) + background.rejectProviderRequestByOrigin(origin) } } -/** - * Exposes the given account(s) to the given origin. - * Call ONLY as a result of direct user action. - */ -function legacyExposeAccounts (origin, accounts) { +function clearApprovedOrigins () { return () => { - return background.legacyExposeAccounts(origin, accounts) + background.clearApprovedOrigins() } } -/** - * Clears the given permissions for the given origin. - */ -function removePermissionsFor (domains) { - return () => { - background.removePermissionsFor(domains) - } -} - -/** - * Clears all permissions for all domains. - */ -function clearPermissions () { - return () => { - background.clearPermissions() - } -} - -// //// - function setFirstTimeFlowType (type) { return (dispatch) => { log.debug(`background.setFirstTimeFlowType`) @@ -2891,6 +2837,12 @@ function getTokenParams (tokenAddress) { } } +function unsetMigratedPrivacyMode () { + return () => { + background.unsetMigratedPrivacyMode() + } +} + function setSeedPhraseBackedUp (seedPhraseBackupState) { return (dispatch) => { log.debug(`background.setSeedPhraseBackedUp`) @@ -3028,46 +2980,3 @@ function getNextNonce () { }) } } - -function setRequestAccountTabIds (requestAccountTabIds) { - return { - type: actions.SET_REQUEST_ACCOUNT_TABS, - value: requestAccountTabIds, - } -} - -function getRequestAccountTabIds () { - return async (dispatch) => { - const requestAccountTabIds = await pify(background.getRequestAccountTabIds).call(background) - dispatch(setRequestAccountTabIds(requestAccountTabIds)) - } -} - -function setOpenMetamaskTabsIDs (openMetaMaskTabIDs) { - return { - type: actions.SET_OPEN_METAMASK_TAB_IDS, - value: openMetaMaskTabIDs, - } -} - -function getOpenMetamaskTabsIds () { - return async (dispatch) => { - const openMetaMaskTabIDs = await pify(background.getOpenMetamaskTabsIds).call(background) - dispatch(setOpenMetamaskTabsIDs(openMetaMaskTabIDs)) - } -} - -function setCurrentWindowTab (currentWindowTab) { - return { - type: actions.SET_CURRENT_WINDOW_TAB, - value: currentWindowTab, - } -} - - -function getCurrentWindowTab () { - return async (dispatch) => { - const currentWindowTab = await global.platform.currentTab() - dispatch(setCurrentWindowTab(currentWindowTab)) - } -} diff --git a/ui/index.js b/ui/index.js index 6f7a43963..058c09be0 100644 --- a/ui/index.js +++ b/ui/index.js @@ -1,5 +1,5 @@ -import React from 'react' const render = require('react-dom').render +const h = require('react-hyperscript') const Root = require('./app/pages') const actions = require('./app/store/actions') const configureStore = require('./app/store/store') @@ -13,13 +13,11 @@ module.exports = launchMetamaskUi log.setLevel(global.METAMASK_DEBUG ? 'debug' : 'warn') function launchMetamaskUi (opts, cb) { - const {backgroundConnection} = opts + var {backgroundConnection} = opts actions._setBackgroundConnection(backgroundConnection) // check if we are unlocked first backgroundConnection.getState(function (err, metamaskState) { - if (err) { - return cb(err) - } + if (err) return cb(err) startApp(metamaskState, backgroundConnection, opts) .then((store) => { cb(null, store) @@ -29,9 +27,7 @@ function launchMetamaskUi (opts, cb) { async function startApp (metamaskState, backgroundConnection, opts) { // parse opts - if (!metamaskState.featureFlags) { - metamaskState.featureFlags = {} - } + if (!metamaskState.featureFlags) metamaskState.featureFlags = {} const currentLocaleMessages = metamaskState.currentLocale ? await fetchLocale(metamaskState.currentLocale) @@ -96,11 +92,11 @@ async function startApp (metamaskState, backgroundConnection, opts) { // start app render( - , - opts.container, - ) + h(Root, { + // inject initial state + store: store, + } + ), opts.container) return store } diff --git a/ui/lib/icon-factory.js b/ui/lib/icon-factory.js index cbaa496cd..2ea943297 100644 --- a/ui/lib/icon-factory.js +++ b/ui/lib/icon-factory.js @@ -1,4 +1,4 @@ -let iconFactory +var iconFactory const isValidAddress = require('ethereumjs-util').isValidAddress const { checksumAddress } = require('../app/helpers/utils/util') const contractMap = require('eth-contract-metadata') @@ -26,18 +26,18 @@ IconFactory.prototype.iconForAddress = function (address, diameter) { // returns svg dom element IconFactory.prototype.generateIdenticonSvg = function (address, diameter) { - const cacheId = `${address}:${diameter}` + var cacheId = `${address}:${diameter}` // check cache, lazily generate and populate cache - const identicon = this.cache[cacheId] || (this.cache[cacheId] = this.generateNewIdenticon(address, diameter)) + var identicon = this.cache[cacheId] || (this.cache[cacheId] = this.generateNewIdenticon(address, diameter)) // create a clean copy so you can modify it - const cleanCopy = identicon.cloneNode(true) + var cleanCopy = identicon.cloneNode(true) return cleanCopy } // creates a new identicon IconFactory.prototype.generateNewIdenticon = function (address, diameter) { - const numericRepresentation = jsNumberForAddress(address) - const identicon = this.jazzicon(diameter, numericRepresentation) + var numericRepresentation = jsNumberForAddress(address) + var identicon = this.jazzicon(diameter, numericRepresentation) return identicon } @@ -58,8 +58,8 @@ function imageElFor (address) { } function jsNumberForAddress (address) { - const addr = address.slice(2, 10) - const seed = parseInt(addr, 16) + var addr = address.slice(2, 10) + var seed = parseInt(addr, 16) return seed } diff --git a/ui/lib/persistent-form.js b/ui/lib/persistent-form.js index 112e4415b..d4dc20b03 100644 --- a/ui/lib/persistent-form.js +++ b/ui/lib/persistent-form.js @@ -15,7 +15,7 @@ PersistentForm.prototype.componentDidMount = function () { const fields = document.querySelectorAll('[data-persistent-formid]') const store = this.getPersistentStore() - for (let i = 0; i < fields.length; i++) { + for (var i = 0; i < fields.length; i++) { const field = fields[i] const key = field.getAttribute('data-persistent-formid') const cached = store[key] @@ -52,7 +52,7 @@ PersistentForm.prototype.persistentFieldDidUpdate = function (event) { PersistentForm.prototype.componentWillUnmount = function () { const fields = document.querySelectorAll('[data-persistent-formid]') - for (let i = 0; i < fields.length; i++) { + for (var i = 0; i < fields.length; i++) { const field = fields[i] field.removeEventListener(eventName, this.persistentFieldDidUpdate.bind(this)) } diff --git a/yarn.lock b/yarn.lock index 867488627..5e9578a16 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1985,18 +1985,6 @@ scroll "^2.0.3" warning "^3.0.0" -"@metamask/forwarder@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@metamask/forwarder/-/forwarder-1.0.0.tgz#3e321022a36561cc6e7b7c84df25f600925f4d95" - integrity sha512-ufgPndhZz0oNhRrixiR6cXH/HwtFwurWvbrU8zAZsFnf1hB4L2VB2Wey/P1wStIx+BJJQjyROvCDyPDoz4ny1A== - -"@metamask/onboarding@^0.1.2": - version "0.1.2" - resolved "https://registry.yarnpkg.com/@metamask/onboarding/-/onboarding-0.1.2.tgz#d5126cbb5e593d782645d6236c497e27bd38d3c4" - integrity sha512-+85Z5OxckGuYr5cCoMlpxASu9geJBMYvwkNWqa5qDDEYKZ8eNXHsADcVYFsvBhxFcf87dC7ty1kWljYVEfTIIA== - dependencies: - bowser "^2.5.4" - "@mrmlnc/readdir-enhanced@^2.2.1": version "2.2.1" resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" @@ -3572,6 +3560,11 @@ ansi-red@^0.1.1: dependencies: ansi-wrap "0.1.0" +ansi-regex@^0.2.0, ansi-regex@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-0.2.1.tgz#0d8e946967a3d8143f93e24e298525fc1b2235f9" + integrity sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk= + ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" @@ -3587,6 +3580,11 @@ ansi-regex@^4.1.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== +ansi-styles@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-1.1.0.tgz#eaecbf66cd706882760b2f4691582b8f55d7a7de" + integrity sha1-6uy/Zs1waIJ2Cy9GkVgrj1XXp94= + ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" @@ -4027,11 +4025,10 @@ assert-plus@1.0.0, assert-plus@^1.0.0: integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= assert@^1.1.1, assert@^1.3.0, assert@^1.4.0, assert@^1.4.1: - version "1.5.0" - resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" - integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA== + version "1.4.1" + resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91" + integrity sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE= dependencies: - object-assign "^4.1.1" util "0.10.3" assertion-error@^1.0.1: @@ -5643,11 +5640,6 @@ bowser@^1.7.3: resolved "https://registry.yarnpkg.com/bowser/-/bowser-1.9.3.tgz#6643ae4d783f31683f6d23156976b74183862162" integrity sha512-/gp96UlcFw5DbV2KQPCqTqi0Mb9gZRyDAHiDsGEH+4B/KOQjeoE5lM1PxlVX8DQDvfEfitmC1rW2Oy8fk/XBDg== -bowser@^2.5.4: - version "2.7.0" - resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.7.0.tgz#96eab1fa07fab08c1ec4c75977a7c8ddf8e0fe1f" - integrity sha512-aIlMvstvu8x+34KEiOHD3AsBgdrzg6sxALYiukOWhFvGMbQI6TRP/iY0LMhUrHs56aD6P1G0Z7h45PUJaa5m9w== - boxen@^1.2.1: version "1.3.0" resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b" @@ -6443,6 +6435,17 @@ chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4. escape-string-regexp "^1.0.5" supports-color "^5.3.0" +chalk@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.5.1.tgz#663b3a648b68b55d04690d49167aa837858f2174" + integrity sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ= + dependencies: + ansi-styles "^1.1.0" + escape-string-regexp "^1.0.0" + has-ansi "^0.1.0" + strip-ansi "^0.3.0" + supports-color "^0.2.0" + chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" @@ -7020,7 +7023,7 @@ comma-separated-tokens@^1.0.0: resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.7.tgz#419cd7fb3258b1ed838dc0953167a25e152f5b59" integrity sha512-Jrx3xsP4pPv4AwJUDWY9wOXGtwPXARej6Xd99h4TUGotmf8APuquKMpK+dnD3UgyxK7OEWaisjZz+3b5jtL6xQ== -commander@2, commander@2.11.0, commander@^2.5.0, commander@^2.6.0: +commander@2, commander@2.11.0, commander@^2.3.0, commander@^2.5.0, commander@^2.6.0: version "2.11.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" integrity sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ== @@ -7234,11 +7237,6 @@ contains-path@^0.1.0: resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo= -content-disposition@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" - integrity sha1-DPaLud318r55YcOoUXjLhdunjLQ= - content-disposition@0.5.3, content-disposition@^0.5.2: version "0.5.3" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" @@ -9600,7 +9598,7 @@ escape-html@^1.0.3, escape-html@~1.0.3: resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= -escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.3, escape-string-regexp@^1.0.5: +escape-string-regexp@1.0.5, escape-string-regexp@^1.0.0, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.3, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= @@ -10075,7 +10073,7 @@ eth-hd-keyring@^3.4.0: events "^1.1.1" xtend "^4.0.1" -eth-json-rpc-errors@^1.0.1: +eth-json-rpc-errors@^1.0.1, eth-json-rpc-errors@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/eth-json-rpc-errors/-/eth-json-rpc-errors-1.1.0.tgz#2a4291fb20c0483c99b53286a814ed14ca4efb2e" integrity sha512-AAA76BmwwSR5Mws+ivZUYxoDwMygDuMWxSTEmqDXhRPTExSWe5wuJLT/rSfvPSy9+owSudy67JmyRQ02RAOOYQ== @@ -11310,13 +11308,6 @@ fast-safe-stringify@^2.0.6, fast-safe-stringify@^2.0.7: resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743" integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA== -fast-url-parser@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/fast-url-parser/-/fast-url-parser-1.1.3.tgz#f4af3ea9f34d8a271cf58ad2b3759f431f0b318d" - integrity sha1-9K8+qfNNiicc9YrSs3WfQx8LMY0= - dependencies: - punycode "^1.3.2" - fast-write-atomic@~0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/fast-write-atomic/-/fast-write-atomic-0.2.1.tgz#7ee8ef0ce3c1f531043c09ae8e5143361ab17ede" @@ -11451,6 +11442,11 @@ file-loader@^3.0.1: loader-utils "^1.0.2" schema-utils "^1.0.0" +file-size@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/file-size/-/file-size-0.0.5.tgz#057d43c3a3ed735da3f90d6052ab380f1e6d5e3b" + integrity sha1-BX1Dw6Ptc12j+Q1gUqs4Dx5tXjs= + file-system-cache@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/file-system-cache/-/file-system-cache-1.0.5.tgz#84259b36a2bbb8d3d6eb1021d3132ffe64cfff4f" @@ -12164,7 +12160,7 @@ fuse.js@^3.4.4: resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-3.4.5.tgz#8954fb43f9729bd5dbcb8c08f251db552595a7a6" integrity sha512-s9PGTaQIkT69HaeoTVjwGsLfb8V8ScJLx5XGFcKHg0MqLUH/UZ4EKOtqtXX9k7AFqCGxD1aJmYb8Q5VYDibVRQ== -gaba@^1.6.0, gaba@^1.9.0: +gaba@^1.9.0: version "1.9.0" resolved "https://registry.yarnpkg.com/gaba/-/gaba-1.9.0.tgz#ccd9f99c56687b5acd39f9e3ceb435b2a59b6aa1" integrity sha512-HoVreAdZssL0jNHuzZ7WP+YKZ0riu44jVDWxhQ9hsgPuzxbVEsz9fO/HDxqAdNZS1Cswayq6+ciZ3HSCFWMKbQ== @@ -13292,6 +13288,13 @@ har-validator@~5.1.0: ajv "^6.5.5" har-schema "^2.0.0" +has-ansi@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-0.1.0.tgz#84f265aae8c0e6a88a12d7022894b7568894c62e" + integrity sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4= + dependencies: + ansi-regex "^0.2.0" + has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" @@ -14247,11 +14250,6 @@ interpret@^1.2.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296" integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw== -intersect-objects@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/intersect-objects/-/intersect-objects-1.0.0.tgz#b7630d28994b89b0f04d44728106136549ce816e" - integrity sha512-MS1xypHKJhWopnJgn4IbitVvt2vFy2KjINQJAPhAtDejZ+ZbMDfyPc6JsS/mWFRt9Eoku4A4usE4f2loEOoeKQ== - into-stream@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/into-stream/-/into-stream-3.1.0.tgz#96fb0a936c12babd6ff1752a17d05616abd094c6" @@ -17601,12 +17599,12 @@ lodash.uniqby@^4.7.0: resolved "https://registry.yarnpkg.com/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz#d99c07a669e9e6d24e1362dfe266c67616af1302" integrity sha1-2ZwHpmnp5tJOE2Lf4mbGdhavEwI= -lodash@4.17.14, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.8.0, lodash@~4.17.10, lodash@~4.17.2, lodash@~4.17.4: +lodash@4.17.14, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.8.0, lodash@~4.17.10, lodash@~4.17.2, lodash@~4.17.4: version "4.17.14" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.14.tgz#9ce487ae66c96254fe20b599f21b6816028078ba" integrity sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw== -lodash@=3.10.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15: +lodash@=3.10.1, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== @@ -18218,14 +18216,12 @@ mersenne-twister@^1.0.1: resolved "https://registry.yarnpkg.com/mersenne-twister/-/mersenne-twister-1.1.0.tgz#f916618ee43d7179efcf641bec4531eb9670978a" integrity sha1-+RZhjuQ9cXnvz2Qb7EUx65Zwl4o= -metamask-inpage-provider@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/metamask-inpage-provider/-/metamask-inpage-provider-4.0.2.tgz#50d9e46b5fdd6610ce185a165004e3c6b762dbb3" - integrity sha512-GXoMa7rP+fx9CriCA+RPHjvJJpfy9531eRdMvbDKv0q95/1pvtzYkj6BdzjxtbM91n4zYl6tmeKDILu+le9Qog== +metamask-inpage-provider@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/metamask-inpage-provider/-/metamask-inpage-provider-3.0.0.tgz#3b9d4bae6f67962b6a7b1a9ee1efaf424f67b6f4" + integrity sha512-44bBCbQwcFF/XGaXSweCWHJaslKhJEFgvcHdxZf9Fm1QfK7VN4U3iAI0BVOLAIkRg0xV3w7xYGLpx2cM1BU7Qw== dependencies: - eth-json-rpc-errors "^2.0.0" - fast-deep-equal "^2.0.1" - json-rpc-engine "^5.1.5" + json-rpc-engine "^5.1.3" json-rpc-middleware-stream "^2.1.1" loglevel "^1.6.1" obj-multiplex "^1.0.0" @@ -18315,23 +18311,11 @@ mime-db@^1.28.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.41.0.tgz#9110408e1f6aa1b34aef51f2c9df3caddf46b6a0" integrity sha512-B5gxBI+2K431XW8C2rcc/lhppbuji67nf9v39eH8pkWoZDxnAL0PxdpH32KYRScniF8qDHBDlI+ipgg5WrCUYw== -mime-db@~1.33.0: - version "1.33.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" - integrity sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ== - mime-db@~1.38.0: version "1.38.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.38.0.tgz#1a2aab16da9eb167b49c6e4df2d9c68d63d8e2ad" integrity sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg== -mime-types@2.1.18: - version "2.1.18" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" - integrity sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ== - dependencies: - mime-db "~1.33.0" - mime-types@^2.1.12, mime-types@^2.1.21, mime-types@~2.1.16, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24: version "2.1.24" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81" @@ -18351,7 +18335,7 @@ mime@1.6.0, mime@^1.6.0: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== -mime@^1.4.1: +mime@^1.2.11, mime@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" integrity sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ== @@ -18897,11 +18881,6 @@ nanoid@^2.0.0: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-2.0.3.tgz#dde999e173bc9d7bd2ee2746b89909ade98e075e" integrity sha512-NbaoqdhIYmY6FXDRB4eYtDVC9Z9eCbn8TyaiC16LNKtpPv/aqa0tOPD8y6gNE4yUNnaZ7LLhYtXOev/6+cBtfw== -nanoid@^2.1.6: - version "2.1.6" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-2.1.6.tgz#0665418f692e54cf44f34d4010761f3240a03314" - integrity sha512-2NDzpiuEy3+H0AVtdt8LoFi7PnqkOnIzYmJQp7xsEU6VexLluHQwKREuiz57XaQC5006seIadPrIZJhyS2n7aw== - nanomatch@^1.2.9: version "1.2.9" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.9.tgz#879f7150cb2dab7a471259066c104eee6e0fa7c2" @@ -19787,6 +19766,13 @@ opn@5.4.0: dependencies: is-wsl "^1.1.0" +opn@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/opn/-/opn-5.2.0.tgz#71fdf934d6827d676cecbea1531f95d354641225" + integrity sha512-Jd/GpzPyHF4P2/aNOVmS3lfMSWV9J7cOhCG1s08XCEAsPkB7lp6ddiU0J7XzyQRDUh8BqJ7PchfINjR8jyofRQ== + dependencies: + is-wsl "^1.1.0" + optimist@0.6.x, optimist@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" @@ -20457,7 +20443,7 @@ path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= -path-is-inside@1.0.2, path-is-inside@^1.0.1, path-is-inside@^1.0.2: +path-is-inside@^1.0.1, path-is-inside@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= @@ -20494,11 +20480,6 @@ path-to-regexp@0.1.7: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= -path-to-regexp@2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.2.1.tgz#90b617025a16381a879bc82a38d4e8bdeb2bcf45" - integrity sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ== - path-to-regexp@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d" @@ -21947,11 +21928,6 @@ randomhex@0.1.5: resolved "https://registry.yarnpkg.com/randomhex/-/randomhex-0.1.5.tgz#baceef982329091400f2a2912c6cd02f1094f585" integrity sha1-us7vmCMpCRQA8qKRLGzQLxCU9YU= -range-parser@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" - integrity sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4= - range-parser@^1.2.0, range-parser@^1.2.1, range-parser@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" @@ -23681,21 +23657,6 @@ rn-host-detect@^1.1.5: resolved "https://registry.yarnpkg.com/rn-host-detect/-/rn-host-detect-1.1.5.tgz#fbecb982b73932f34529e97932b9a63e58d8deb6" integrity sha512-ufk2dFT3QeP9HyZ/xTuMtW27KnFy815CYitJMqQm+pgG3ZAtHBsrU8nXizNKkqXGy3bQmhEoloVbrfbvMJMqkg== -rpc-cap@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/rpc-cap/-/rpc-cap-1.0.1.tgz#c19f6651d9d003256c73831422e0bd60b4fa8b55" - integrity sha512-M75F5IfohYkwGvitWmstimP9OL+9h10m1ZRC2zCB1Nli4EPzL8n5re58xlrcOnwOO38FdSSPfcwcCzMuVT8K2g== - dependencies: - clone "^2.1.2" - eth-json-rpc-errors "^2.0.0" - fast-deep-equal "^2.0.1" - gaba "^1.6.0" - intersect-objects "^1.0.0" - is-subset "^0.1.1" - json-rpc-engine "^5.1.3" - obs-store "^4.0.3" - uuid "^3.3.2" - rsa-pem-to-jwk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/rsa-pem-to-jwk/-/rsa-pem-to-jwk-1.1.3.tgz#245e76bdb7e7234cfee7ca032d31b54c38fab98e" @@ -23831,11 +23792,6 @@ safe-json-parse@~1.0.1: resolved "https://registry.yarnpkg.com/safe-json-parse/-/safe-json-parse-1.0.1.tgz#3e76723e38dfdda13c9b1d29a1e07ffee4b30b57" integrity sha1-PnZyPjjf3aE8mx0poeB//uSzC1c= -safe-json-stringify@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz#356e44bc98f1f93ce45df14bcd7c01cda86e0afd" - integrity sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg== - safe-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" @@ -24192,20 +24148,6 @@ serve-favicon@^2.5.0: parseurl "~1.3.2" safe-buffer "5.1.1" -serve-handler@^6.1.2: - version "6.1.2" - resolved "https://registry.yarnpkg.com/serve-handler/-/serve-handler-6.1.2.tgz#f05b0421a313fff2d257838cba00cbcc512cd2b6" - integrity sha512-RFh49wX7zJmmOVDcIjiDSJnMH+ItQEvyuYLYuDBVoA/xmQSCuj+uRmk1cmBB5QQlI3qOiWKp6p4DUGY+Z5AB2A== - dependencies: - bytes "3.0.0" - content-disposition "0.5.2" - fast-url-parser "1.1.3" - mime-types "2.1.18" - minimatch "3.0.4" - path-is-inside "1.0.2" - path-to-regexp "2.2.1" - range-parser "1.2.0" - serve-static@1.14.1: version "1.14.1" resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" @@ -25106,6 +25048,17 @@ static-module@^2.2.0: static-eval "^2.0.0" through2 "~2.0.3" +static-server@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/static-server/-/static-server-2.2.1.tgz#49e3cae2a001736b0ee9e95d21d3d843fc95efaa" + integrity sha512-j5eeW6higxYNmXMIT8iHjsdiViTpQDthg7o+SHsRtqdbxscdHqBHXwrXjHC8hL3F0Tsu34ApUpDkwzMBPBsrLw== + dependencies: + chalk "^0.5.1" + commander "^2.3.0" + file-size "0.0.5" + mime "^1.2.11" + opn "^5.2.0" + "statuses@>= 1.3.1 < 2": version "1.3.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" @@ -25410,6 +25363,13 @@ strip-ansi@5.2.0, strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" +strip-ansi@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-0.3.0.tgz#25f48ea22ca79187f3174a4db8759347bb126220" + integrity sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA= + dependencies: + ansi-regex "^0.2.1" + strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" @@ -25679,6 +25639,11 @@ supports-color@4.4.0: dependencies: has-flag "^2.0.0" +supports-color@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-0.2.0.tgz#d92de2694eb3f67323973d7ae3d8b55b4c22190a" + integrity sha1-2S3iaU6z9nMjlz1649i1W0wiGQo= + supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -27252,7 +27217,7 @@ uuid@3.2.1: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14" integrity sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA== -uuid@3.3.2, uuid@^3.0.1, uuid@^3.1.0, uuid@^3.2.1, uuid@^3.2.2: +uuid@3.3.2, uuid@^3.0.1, uuid@^3.1.0, uuid@^3.2.1, uuid@^3.2.2, uuid@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== @@ -27262,7 +27227,7 @@ uuid@^2.0.1: resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a" integrity sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho= -uuid@^3.3.2, uuid@^3.3.3: +uuid@^3.3.3: version "3.3.3" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866" integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ== From aeafc1e8bccfe3c9448b7e9745797a0c23282c8f Mon Sep 17 00:00:00 2001 From: ricky Date: Thu, 21 Nov 2019 16:59:50 -0500 Subject: [PATCH 02/70] Add overflow hidden (#7488) --- ui/app/components/app/transaction-list-item/index.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/app/components/app/transaction-list-item/index.scss b/ui/app/components/app/transaction-list-item/index.scss index e0c62199e..9804ecd97 100644 --- a/ui/app/components/app/transaction-list-item/index.scss +++ b/ui/app/components/app/transaction-list-item/index.scss @@ -139,6 +139,7 @@ &__expander { max-height: 0px; width: 100%; + overflow: hidden; &--show { max-height: 1000px; From 92951556207df4b280b2c7335d06a8fbde8d31df Mon Sep 17 00:00:00 2001 From: Thomas Date: Thu, 21 Nov 2019 17:17:08 -0800 Subject: [PATCH 03/70] Change component props from string to object and associated tests to mimic actual in-use component Add test to updateGas when asset is changed --- .../pages/send/tests/send-component.test.js | 40 +++++++++++++------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/ui/app/pages/send/tests/send-component.test.js b/ui/app/pages/send/tests/send-component.test.js index d5b43bbe4..19ea88626 100644 --- a/ui/app/pages/send/tests/send-component.test.js +++ b/ui/app/pages/send/tests/send-component.test.js @@ -57,10 +57,10 @@ describe('Send Component', function () { primaryCurrency="mockPrimaryCurrency" recentBlocks={['mockBlock']} selectedAddress="mockSelectedAddress" - selectedToken="mockSelectedToken" + selectedToken={{ address: 'mockTokenAddress', decimals: 18, symbol: 'TST' }} showHexData tokenBalance="mockTokenBalance" - tokenContract="mockTokenContract" + tokenContract={{ method: 'mockTokenMethod' }} updateAndSetGasLimit={propsMethodSpies.updateAndSetGasLimit} updateSendErrors={propsMethodSpies.updateSendErrors} updateSendTokenBalance={propsMethodSpies.updateSendTokenBalance} @@ -79,6 +79,7 @@ describe('Send Component', function () { propsMethodSpies.updateAndSetGasLimit.resetHistory() propsMethodSpies.updateSendErrors.resetHistory() propsMethodSpies.updateSendTokenBalance.resetHistory() + propsMethodSpies.updateToNicknameIfNecessary.resetHistory() }) it('should call componentDidMount', () => { @@ -129,7 +130,7 @@ describe('Send Component', function () { prevBalance: '', prevGasTotal: undefined, prevTokenBalance: undefined, - selectedToken: 'mockSelectedToken', + selectedToken: { address: 'mockTokenAddress', decimals: 18, symbol: 'TST' }, tokenBalance: 'mockTokenBalance', } ) @@ -162,7 +163,7 @@ describe('Send Component', function () { conversionRate: 10, gasTotal: 'mockGasTotal', primaryCurrency: 'mockPrimaryCurrency', - selectedToken: 'mockSelectedToken', + selectedToken: { address: 'mockTokenAddress', decimals: 18, symbol: 'TST' }, tokenBalance: 'mockTokenBalance', } ) @@ -184,7 +185,7 @@ describe('Send Component', function () { conversionRate: 10, gasTotal: 'mockGasTotal', primaryCurrency: 'mockPrimaryCurrency', - selectedToken: 'mockSelectedToken', + selectedToken: { address: 'mockTokenAddress', decimals: 18, symbol: 'TST' }, } ) }) @@ -225,7 +226,7 @@ describe('Send Component', function () { it('should call updateSendErrors with the expected params if selectedToken is truthy', () => { propsMethodSpies.updateSendErrors.resetHistory() - wrapper.setProps({ selectedToken: 'someToken' }) + wrapper.setProps({ selectedToken: { address: 'mockTokenAddress', decimals: 18, symbol: 'TST' }}) wrapper.instance().componentDidUpdate({ from: { balance: 'balanceChanged', @@ -246,6 +247,7 @@ describe('Send Component', function () { balance: 'balanceChanged', }, network: '3', + selectedToken: { address: 'mockTokenAddress', decimals: 18, symbol: 'TST' }, // Make sure not to hit updateGas when changing asset }) assert.equal(propsMethodSpies.updateSendTokenBalance.callCount, 0) assert.equal(SendTransactionScreen.prototype.updateGas.callCount, 0) @@ -260,6 +262,7 @@ describe('Send Component', function () { balance: 'balanceChanged', }, network: '3', + selectedToken: { address: 'mockTokenAddress', decimals: 18, symbol: 'TST' }, // Make sure not to hit updateGas when changing asset }) assert.equal(propsMethodSpies.updateSendTokenBalance.callCount, 0) assert.equal(SendTransactionScreen.prototype.updateGas.callCount, 0) @@ -273,13 +276,14 @@ describe('Send Component', function () { balance: 'balanceChanged', }, network: '2', + selectedToken: { address: 'mockTokenAddress', decimals: 18, symbol: 'TST' }, // Make sure not to hit updateGas when changing asset }) assert.equal(propsMethodSpies.updateSendTokenBalance.callCount, 1) assert.deepEqual( propsMethodSpies.updateSendTokenBalance.getCall(0).args[0], { - selectedToken: 'mockSelectedToken', - tokenContract: 'mockTokenContract', + selectedToken: { address: 'mockTokenAddress', decimals: 18, symbol: 'TST' }, // Make sure not to hit updateGas when changing asset + tokenContract: { method: 'mockTokenMethod' }, address: 'mockAddress', } ) @@ -289,6 +293,20 @@ describe('Send Component', function () { [] ) }) + + it('should call updateGas when ', () => { + SendTransactionScreen.prototype.updateGas.resetHistory() + propsMethodSpies.updateAndSetGasLimit.resetHistory() + wrapper.instance().componentDidUpdate({ + from: { + balance: 'balancedChanged', + }, + network: '3', // Make sure not to hit updateGas when changing network + selectedToken: { address: 'newSelectedToken' }, + }) + assert.equal(propsMethodSpies.updateToNicknameIfNecessary.callCount, 0) // Network did not change + assert.equal(propsMethodSpies.updateAndSetGasLimit.callCount, 1) + }) }) describe('updateGas', () => { @@ -305,7 +323,7 @@ describe('Send Component', function () { gasPrice: 'mockGasPrice', recentBlocks: ['mockBlock'], selectedAddress: 'mockSelectedAddress', - selectedToken: 'mockSelectedToken', + selectedToken: { address: 'mockTokenAddress', decimals: 18, symbol: 'TST' }, to: '', value: 'mockAmount', data: undefined, @@ -431,9 +449,7 @@ describe('Send Component', function () { }) it('should warn when send to a known token contract address', () => { - wrapper.setProps({ - selectedToken: '0x888', - }) + wrapper.setProps({ address: '0x888', decimals: 18, symbol: '888' }) const instance = wrapper.instance() instance.onRecipientInputChange('0x13cb85823f78Cff38f0B0E90D3e975b8CB3AAd64') From 42813508bca3a4467d0ee309c9f2be804d7a9efe Mon Sep 17 00:00:00 2001 From: Thomas Date: Thu, 21 Nov 2019 17:19:33 -0800 Subject: [PATCH 04/70] Update Gas when asset is changed --- ui/app/pages/send/send.component.js | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/app/pages/send/send.component.js b/ui/app/pages/send/send.component.js index d699e27a7..54e9a8afe 100644 --- a/ui/app/pages/send/send.component.js +++ b/ui/app/pages/send/send.component.js @@ -165,6 +165,7 @@ export default class SendTransactionScreen extends PersistentForm { if (selectedTokenAddress && prevTokenAddress !== selectedTokenAddress) { this.updateSendToken() + this.updateGas() } } From 767f8ec52f603ef8cea8e3e26b6a5052c3f0e26e Mon Sep 17 00:00:00 2001 From: Thomas Date: Thu, 21 Nov 2019 17:21:24 -0800 Subject: [PATCH 05/70] Finish test description --- ui/app/pages/send/tests/send-component.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/pages/send/tests/send-component.test.js b/ui/app/pages/send/tests/send-component.test.js index 19ea88626..98ac55f91 100644 --- a/ui/app/pages/send/tests/send-component.test.js +++ b/ui/app/pages/send/tests/send-component.test.js @@ -294,7 +294,7 @@ describe('Send Component', function () { ) }) - it('should call updateGas when ', () => { + it('should call updateGas when selectedToken.address is changed', () => { SendTransactionScreen.prototype.updateGas.resetHistory() propsMethodSpies.updateAndSetGasLimit.resetHistory() wrapper.instance().componentDidUpdate({ From 4190b397d1a625b2d03b21af7f58c77ebb00139e Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Fri, 22 Nov 2019 17:23:27 -0330 Subject: [PATCH 06/70] Remove unused onClick prop from Dropdown component (#7500) --- ui/app/components/app/dropdowns/components/dropdown.js | 1 - 1 file changed, 1 deletion(-) diff --git a/ui/app/components/app/dropdowns/components/dropdown.js b/ui/app/components/app/dropdowns/components/dropdown.js index cc966ffa5..fd7055803 100644 --- a/ui/app/components/app/dropdowns/components/dropdown.js +++ b/ui/app/components/app/dropdowns/components/dropdown.js @@ -58,7 +58,6 @@ Dropdown.defaultProps = { Dropdown.propTypes = { isOpen: PropTypes.bool.isRequired, - onClick: PropTypes.func.isRequired, children: PropTypes.node, style: PropTypes.object.isRequired, onClickOutside: PropTypes.func, From 5798a9e5f138924f5a790e0158f32a3fe0bfa853 Mon Sep 17 00:00:00 2001 From: Bruno Barbieri Date: Fri, 22 Nov 2019 17:25:44 -0500 Subject: [PATCH 07/70] Fix chainId for non standard networks (#7502) --- app/scripts/lib/select-chain-id.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/scripts/lib/select-chain-id.js b/app/scripts/lib/select-chain-id.js index 3171c9840..d94b35898 100644 --- a/app/scripts/lib/select-chain-id.js +++ b/app/scripts/lib/select-chain-id.js @@ -15,8 +15,8 @@ const standardNetworkId = { } function selectChainId (metamaskState) { - const { network, provider: { chaindId } } = metamaskState - return standardNetworkId[network] || `0x${parseInt(chaindId, 10).toString(16)}` + const { network, provider: { chainId } } = metamaskState + return standardNetworkId[network] || `0x${parseInt(chainId, 10).toString(16)}` } module.exports = selectChainId From e673bfb25c71cec431a4c6a149b84ac94d880b16 Mon Sep 17 00:00:00 2001 From: ryanml Date: Fri, 22 Nov 2019 18:54:59 -0700 Subject: [PATCH 08/70] Fixing hardware connect error display (#7519) --- .../pages/create-account/connect-hardware/index.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/ui/app/pages/create-account/connect-hardware/index.js b/ui/app/pages/create-account/connect-hardware/index.js index 66851c780..ff8506142 100644 --- a/ui/app/pages/create-account/connect-hardware/index.js +++ b/ui/app/pages/create-account/connect-hardware/index.js @@ -116,10 +116,11 @@ class ConnectHardwareForm extends Component { } }) .catch(e => { - if (e === 'Window blocked') { + const errorMessage = e.message + if (errorMessage === 'Window blocked') { this.setState({ browserSupported: false, error: null}) - } else if (e !== 'Window closed' && e !== 'Popup closed') { - this.setState({ error: e.toString() }) + } else if (errorMessage !== 'Window closed' && errorMessage !== 'Popup closed') { + this.setState({ error: errorMessage }) } }) } @@ -134,7 +135,7 @@ class ConnectHardwareForm extends Component { unlocked: false, }) }).catch(e => { - this.setState({ error: e.toString() }) + this.setState({ error: e.message }) }) } @@ -162,10 +163,10 @@ class ConnectHardwareForm extends Component { name: 'Error connecting hardware wallet', }, customVariables: { - error: e.toString(), + error: e.message, }, }) - this.setState({ error: e.toString() }) + this.setState({ error: e.message }) }) } From c6259f6eb57c2dbb2b1b9aa7077c26b6cf969fb1 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Sat, 23 Nov 2019 01:26:39 -0330 Subject: [PATCH 09/70] Fix accessibility of first-time-flow terms checkboxes (#7501) --- .../import-with-seed-phrase.component.js | 18 ++++++++++--- .../new-account/new-account.component.js | 25 +++++++++++-------- 2 files changed, 30 insertions(+), 13 deletions(-) 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 e1c0b21ed..1fae5351c 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 @@ -175,6 +175,12 @@ export default class ImportWithSeedPhrase extends PureComponent { return !passwordError && !confirmPasswordError && !seedPhraseError } + onTermsKeyPress = ({key}) => { + if (key === ' ' || key === 'Enter') { + this.toggleTermsCheck() + } + } + toggleTermsCheck = () => { this.context.metricsEvent({ eventOpts: { @@ -183,7 +189,6 @@ export default class ImportWithSeedPhrase extends PureComponent { name: 'Check ToS', }, }) - this.setState((prevState) => ({ termsChecked: !prevState.termsChecked, })) @@ -267,10 +272,17 @@ export default class ImportWithSeedPhrase extends PureComponent { largeLabel />
    -
    +
    {termsChecked ? : null}
    - + I have read and agree to the { - const { history } = this.props - - event.preventDefault() - history.push(INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE) - } - toggleTermsCheck = () => { this.context.metricsEvent({ eventOpts: { @@ -136,6 +128,12 @@ export default class NewAccount extends PureComponent { })) } + onTermsKeyPress = ({key}) => { + if (key === ' ' || key === 'Enter') { + this.toggleTermsCheck() + } + } + render () { const { t } = this.context const { password, confirmPassword, passwordError, confirmPasswordError, termsChecked } = this.state @@ -195,10 +193,17 @@ export default class NewAccount extends PureComponent { largeLabel />
    -
    +
    {termsChecked ? : null}
    - + I have read and agree to the
    Date: Tue, 26 Nov 2019 17:14:29 -0330 Subject: [PATCH 10/70] Don't overwrite state when setting inactive timeout Using `#putState` here was clearing the other fields in the store. --- app/scripts/controllers/app-state.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scripts/controllers/app-state.js b/app/scripts/controllers/app-state.js index 8d67874ad..c60a1c4f5 100644 --- a/app/scripts/controllers/app-state.js +++ b/app/scripts/controllers/app-state.js @@ -45,7 +45,7 @@ class AppStateController { * @private */ _setInactiveTimeout (timeoutMinutes) { - this.store.putState({ + this.store.updateState({ timeoutMinutes, }) From ddcf8f9de75ffcf2bb54f952a8c54503d8553cb7 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Tue, 26 Nov 2019 17:54:53 -0330 Subject: [PATCH 11/70] Construct AppStateController with the persisted initial state --- app/scripts/metamask-controller.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 14caf0706..9a43b8a80 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -109,6 +109,7 @@ module.exports = class MetamaskController extends EventEmitter { this.appStateController = new AppStateController({ preferencesStore: this.preferencesController.store, onInactiveTimeout: () => this.setLocked(), + initState: initState.AppStateController, }) // currency controller From ff6b254a9ba4e287eb86e52648ada3ccd16db897 Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Wed, 27 Nov 2019 09:28:03 -0330 Subject: [PATCH 12/70] Ensures the tx controller + state-manager orders transactions as received (#7484) * Ensures the tx controller + tx-state-manager orders transactions in the order they are received * Handle transaction ordering in cases where tx ids are off by more than 1 in tx-state-manager * Add comment to addUnapprovedTransaction explaining calling _determineTransactionCategory after generateTxMeta * Sort txes by timestamp of creation instead of id --- app/scripts/controllers/transactions/index.js | 11 ++++++++--- .../controllers/transactions/tx-state-manager.js | 7 ++++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index df9fb6502..8e4e9c0a7 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -192,13 +192,18 @@ class TransactionController extends EventEmitter { throw new Error(`Transaction from address isn't valid for this account`) } txUtils.validateTxParams(normalizedTxParams) - // construct txMeta - const { transactionCategory, getCodeResponse } = await this._determineTransactionCategory(txParams) + /** + `generateTxMeta` adds the default txMeta properties to the passed object. + These include the tx's `id`. As we use the id for determining order of + txes in the tx-state-manager, it is necessary to call the asynchronous + method `this._determineTransactionCategory` after `generateTxMeta`. + */ let txMeta = this.txStateManager.generateTxMeta({ txParams: normalizedTxParams, type: TRANSACTION_TYPE_STANDARD, - transactionCategory, }) + const { transactionCategory, getCodeResponse } = await this._determineTransactionCategory(txParams) + txMeta.transactionCategory = transactionCategory this.addTx(txMeta) this.emit('newUnapprovedTx', txMeta) diff --git a/app/scripts/controllers/transactions/tx-state-manager.js b/app/scripts/controllers/transactions/tx-state-manager.js index 6a92c0601..cf254352f 100644 --- a/app/scripts/controllers/transactions/tx-state-manager.js +++ b/app/scripts/controllers/transactions/tx-state-manager.js @@ -159,7 +159,12 @@ class TransactionStateManager extends EventEmitter { transactions.splice(index, 1) } } - transactions.push(txMeta) + const newTxIndex = transactions + .findIndex((currentTxMeta) => currentTxMeta.time > txMeta.time) + + newTxIndex === -1 + ? transactions.push(txMeta) + : transactions.splice(newTxIndex, 0, txMeta) this._saveTxList(transactions) return txMeta } From 77449923c33a92b202e47017dd67b9797995af95 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Thu, 28 Nov 2019 18:59:15 -0400 Subject: [PATCH 13/70] Prevent redux state mutation (#7598) The `txParams` property of a transaction in Redux state was being mutated. The mutation is now prevented with a shallow clone. --- ui/app/ducks/metamask/metamask.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ui/app/ducks/metamask/metamask.js b/ui/app/ducks/metamask/metamask.js index 23437610f..8a9739af7 100644 --- a/ui/app/ducks/metamask/metamask.js +++ b/ui/app/ducks/metamask/metamask.js @@ -322,7 +322,9 @@ function reduceMetamask (state, action) { let { selectedAddressTxList } = metamaskState selectedAddressTxList = selectedAddressTxList.map(tx => { if (tx.id === txId) { - tx.txParams = value + const newTx = Object.assign({}, tx) + newTx.txParams = value + return newTx } return tx }) From f72070ae0da949dc163347330f7cb95dc2afe24b Mon Sep 17 00:00:00 2001 From: Xavier Maysonnave Date: Mon, 2 Dec 2019 19:26:02 +0530 Subject: [PATCH 14/70] Process URL fragment for ens-ipfs redirects (#7604) Also use app.ens.domains instead of manager.ens.domains --- app/scripts/lib/ens-ipfs/setup.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/scripts/lib/ens-ipfs/setup.js b/app/scripts/lib/ens-ipfs/setup.js index a3711c5f9..8f19510c7 100644 --- a/app/scripts/lib/ens-ipfs/setup.js +++ b/app/scripts/lib/ens-ipfs/setup.js @@ -26,22 +26,22 @@ function setupEnsIpfsResolver ({ provider }) { if (tabId === -1) return // parse ens name const urlData = urlUtil.parse(url) - const { hostname: name, path, search } = urlData + const { hostname: name, path, search, hash: fragment } = urlData const domainParts = name.split('.') const topLevelDomain = domainParts[domainParts.length - 1] // if unsupported TLD, abort if (!supportedTopLevelDomains.includes(topLevelDomain)) return // otherwise attempt resolve - attemptResolve({ tabId, name, path, search }) + attemptResolve({ tabId, name, path, search, fragment }) } - async function attemptResolve ({ tabId, name, path, search }) { + async function attemptResolve ({ tabId, name, path, search, fragment }) { extension.tabs.update(tabId, { url: `loading.html` }) - let url = `https://manager.ens.domains/name/${name}` + let url = `https://app.ens.domains/name/${name}` try { const {type, hash} = await resolveEnsToIpfsContentId({ provider, name }) if (type === 'ipfs-ns') { - const resolvedUrl = `https://gateway.ipfs.io/ipfs/${hash}${path}${search || ''}` + const resolvedUrl = `https://gateway.ipfs.io/ipfs/${hash}${path}${search || ''}${fragment || ''}` try { // check if ipfs gateway has result const response = await fetch(resolvedUrl, { method: 'HEAD' }) @@ -50,11 +50,11 @@ function setupEnsIpfsResolver ({ provider }) { console.warn(err) } } else if (type === 'swarm-ns') { - url = `https://swarm-gateways.net/bzz:/${hash}${path}${search || ''}` + url = `https://swarm-gateways.net/bzz:/${hash}${path}${search || ''}${fragment || ''}` } else if (type === 'onion' || type === 'onion3') { - url = `http://${hash}.onion${path}${search || ''}` + url = `http://${hash}.onion${path}${search || ''}${fragment || ''}` } else if (type === 'zeronet') { - url = `http://127.0.0.1:43110/${hash}${path}${search || ''}` + url = `http://127.0.0.1:43110/${hash}${path}${search || ''}${fragment || ''}` } } catch (err) { console.warn(err) From cb472edfe425ed5a558bb5349b6f8bacb3f97d06 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Tue, 3 Dec 2019 15:16:59 -0400 Subject: [PATCH 15/70] Fix a typo made comparing previous prop (#7628) The prop `prevIsAccountMenuOpen` was referenced in `prevProps`, despite it not existing. It seems clear from the context that the intention was to check the `isAccountMenuOpen` prop from `prevProps`, and name the local variable `prevIsAccountMenuOpen`. --- ui/app/components/app/account-menu/account-menu.component.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ui/app/components/app/account-menu/account-menu.component.js b/ui/app/components/app/account-menu/account-menu.component.js index 1b81e33a2..6cb2aa4b8 100644 --- a/ui/app/components/app/account-menu/account-menu.component.js +++ b/ui/app/components/app/account-menu/account-menu.component.js @@ -28,7 +28,6 @@ export default class AccountMenu extends PureComponent { history: PropTypes.object, identities: PropTypes.object, isAccountMenuOpen: PropTypes.bool, - prevIsAccountMenuOpen: PropTypes.bool, keyrings: PropTypes.array, lockMetamask: PropTypes.func, selectedAddress: PropTypes.string, @@ -42,7 +41,7 @@ export default class AccountMenu extends PureComponent { } componentDidUpdate (prevProps) { - const { prevIsAccountMenuOpen } = prevProps + const { isAccountMenuOpen: prevIsAccountMenuOpen } = prevProps const { isAccountMenuOpen } = this.props if (!prevIsAccountMenuOpen && isAccountMenuOpen) { From d605fa57ceb334fc6193255dc6ad1383389555b7 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Mon, 25 Nov 2019 10:02:51 -0330 Subject: [PATCH 16/70] Use localized messages for NotificationModal buttons (#7558) --- ui/app/components/app/modals/notification-modal.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ui/app/components/app/modals/notification-modal.js b/ui/app/components/app/modals/notification-modal.js index 84d9004b7..a4282595f 100644 --- a/ui/app/components/app/modals/notification-modal.js +++ b/ui/app/components/app/modals/notification-modal.js @@ -5,6 +5,10 @@ const connect = require('react-redux').connect const actions = require('../../../store/actions') class NotificationModal extends Component { + static contextProps = { + t: PropTypes.func.isRequired, + } + render () { const { header, @@ -15,6 +19,8 @@ class NotificationModal extends Component { onConfirm, } = this.props + const { t } = this.context + const showButtons = showCancelButton || showConfirmButton return h('div', [ @@ -39,14 +45,14 @@ class NotificationModal extends Component { showCancelButton && h('div.btn-default.notification-modal__buttons__btn', { onClick: hideModal, - }, 'Cancel'), + }, t('cancel')), showConfirmButton && h('div.button.btn-secondary.notification-modal__buttons__btn', { onClick: () => { onConfirm() hideModal() }, - }, 'Confirm'), + }, t('confirm')), ]), From 28ecc6c34952a116ae12d8434b0dad3b2da5e93e Mon Sep 17 00:00:00 2001 From: MetaMask Bot Date: Wed, 4 Dec 2019 01:39:26 +0000 Subject: [PATCH 17/70] Version v7.7.1 --- CHANGELOG.md | 2 ++ app/manifest.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 02d6a0b64..ce85f60ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Develop Branch +## 7.7.1 Wed Dec 04 2019 + ## 7.6.1 Tue Nov 19 2019 - [#7475](https://github.com/MetaMask/metamask-extension/pull/7475): Add 'Remind Me Later' to the Maker notification - [#7436](https://github.com/MetaMask/metamask-extension/pull/7436): Add additional rpcUrl verification diff --git a/app/manifest.json b/app/manifest.json index f9e25ceda..299158d7f 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "short_name": "__MSG_appName__", - "version": "7.6.1", + "version": "7.7.1", "manifest_version": 2, "author": "https://metamask.io", "description": "__MSG_appDescription__", From 6ce3bd8372b0c51c5850a96db6c76e73475559c0 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Wed, 4 Dec 2019 15:47:33 -0400 Subject: [PATCH 18/70] Update changelog for v7.7.1 This includes the changelog for v7.7.0, which is included with the label `[WITHDRAWN]` to denote that it was rolled back. --- CHANGELOG.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce85f60ac..30554afd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,31 @@ ## Current Develop Branch ## 7.7.1 Wed Dec 04 2019 +- [#7488](https://github.com/MetaMask/metamask-extension/pull/7488): Fix text overlap when expanding transaction +- [#7491](https://github.com/MetaMask/metamask-extension/pull/7491): Update gas when asset is changed on send screen +- [#7500](https://github.com/MetaMask/metamask-extension/pull/7500): Remove unused onClick prop from Dropdown component +- [#7502](https://github.com/MetaMask/metamask-extension/pull/7502): Fix chainId for non standard networks +- [#7519](https://github.com/MetaMask/metamask-extension/pull/7519): Fixing hardware connect error display +- [#7501](https://github.com/MetaMask/metamask-extension/pull/7501): Fix accessibility of first-time-flow terms checkboxes +- [#7579](https://github.com/MetaMask/metamask-extension/pull/7579): Prevent Maker migration dismissal timeout state from being overwritten +- [#7581](https://github.com/MetaMask/metamask-extension/pull/7581): Persist Maker migration dismissal timeout +- [#7484](https://github.com/MetaMask/metamask-extension/pull/7484): Ensure transactions are shown in the order they are received +- [#7604](https://github.com/MetaMask/metamask-extension/pull/7604): Process URL fragment for ens-ipfs redirects +- [#7628](https://github.com/MetaMask/metamask-extension/pull/7628): Fix typo that resulted in degrated account menu performance +- [#7558](https://github.com/MetaMask/metamask-extension/pull/7558): Use localized messages for NotificationModal buttons + +## 7.7.0 Thu Nov 28 2019 [WITHDRAWN] +- [#7004](https://github.com/MetaMask/metamask-extension/pull/7004): Connect distinct accounts per site +- [#7480](https://github.com/MetaMask/metamask-extension/pull/7480): Fixed link on root README.md +- [#7482](https://github.com/MetaMask/metamask-extension/pull/7482): Update Wyre ETH purchase url +- [#7484](https://github.com/MetaMask/metamask-extension/pull/7484): Ensure transactions are shown in the order they are received +- [#7491](https://github.com/MetaMask/metamask-extension/pull/7491): Update gas when token is changed on the send screen +- [#7501](https://github.com/MetaMask/metamask-extension/pull/7501): Fix accessibility of first-time-flow terms checkboxes +- [#7502](https://github.com/MetaMask/metamask-extension/pull/7502): Fix chainId for non standard networks +- [#7579](https://github.com/MetaMask/metamask-extension/pull/7579): Fix timing of DAI migration notifications after dismissal +- [#7519](https://github.com/MetaMask/metamask-extension/pull/7519): Fixing hardware connect error display +- [#7558](https://github.com/MetaMask/metamask-extension/pull/7558): Use localized messages for NotificationModal buttons +- [#7488](https://github.com/MetaMask/metamask-extension/pull/7488): Fix text overlap when expanding transaction ## 7.6.1 Tue Nov 19 2019 - [#7475](https://github.com/MetaMask/metamask-extension/pull/7475): Add 'Remind Me Later' to the Maker notification From 77b42c4f4b4e2838f5b09da13072363580cfeb7d Mon Sep 17 00:00:00 2001 From: MetaMask Bot Date: Fri, 10 Jan 2020 14:57:42 +0000 Subject: [PATCH 19/70] Version v7.7.2 --- CHANGELOG.md | 2 ++ app/manifest.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30554afd9..be3177eea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Develop Branch +## 7.7.2 Fri Jan 10 2020 + ## 7.7.1 Wed Dec 04 2019 - [#7488](https://github.com/MetaMask/metamask-extension/pull/7488): Fix text overlap when expanding transaction - [#7491](https://github.com/MetaMask/metamask-extension/pull/7491): Update gas when asset is changed on send screen diff --git a/app/manifest.json b/app/manifest.json index 299158d7f..7818b046b 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "short_name": "__MSG_appName__", - "version": "7.7.1", + "version": "7.7.2", "manifest_version": 2, "author": "https://metamask.io", "description": "__MSG_appDescription__", From 7b8e87e32a7978ce864ce162949f9bb0295bc53c Mon Sep 17 00:00:00 2001 From: Dan J Miller Date: Fri, 10 Jan 2020 10:53:54 -0330 Subject: [PATCH 20/70] Fix gas estimate for tokens (#7753) * Make gas estimate update on debounced token amount change, not just on blur after change * Updated tests * Ensure `updateGas` is bound early Co-authored-by: Mark Stacey --- test/e2e/metamask-ui.spec.js | 1 + .../ui/token-input/token-input.component.js | 2 +- .../send-amount-row.component.js | 7 +++-- .../tests/send-amount-row-component.test.js | 12 ++++---- .../send/send-footer/send-footer.component.js | 5 ++-- .../send/send-footer/send-footer.container.js | 2 ++ .../tests/send-footer-component.test.js | 14 ++++++++++ .../tests/send-footer-container.test.js | 28 +------------------ 8 files changed, 33 insertions(+), 38 deletions(-) diff --git a/test/e2e/metamask-ui.spec.js b/test/e2e/metamask-ui.spec.js index a0efe56f9..38c7ac528 100644 --- a/test/e2e/metamask-ui.spec.js +++ b/test/e2e/metamask-ui.spec.js @@ -965,6 +965,7 @@ describe('MetaMask', function () { // Continue to next screen const nextScreen = await findElement(driver, By.xpath(`//button[contains(text(), 'Next')]`)) + await driver.wait(until.elementIsEnabled(nextScreen)) await nextScreen.click() await delay(regularDelayMs) }) diff --git a/ui/app/components/ui/token-input/token-input.component.js b/ui/app/components/ui/token-input/token-input.component.js index c28af5fde..316463f5b 100644 --- a/ui/app/components/ui/token-input/token-input.component.js +++ b/ui/app/components/ui/token-input/token-input.component.js @@ -78,7 +78,7 @@ export default class TokenInput extends PureComponent { } handleBlur = () => { - this.props.onBlur(this.state.hexValue) + this.props.onBlur && this.props.onBlur(this.state.hexValue) } renderConversionComponent () { diff --git a/ui/app/pages/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 495de58b5..0b46d48c5 100644 --- a/ui/app/pages/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 @@ -1,5 +1,6 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' +import debounce from 'lodash.debounce' import SendRowWrapper from '../send-row-wrapper' import AmountMaxButton from './amount-max-button' import UserPreferencedCurrencyInput from '../../../../components/app/user-preferenced-currency-input' @@ -32,6 +33,8 @@ export default class SendAmountRow extends Component { t: PropTypes.func, } + updateGas = debounce(this.updateGas.bind(this), 500) + validateAmount (amount) { const { amountConversionRate, @@ -90,8 +93,8 @@ export default class SendAmountRow extends Component { return ( this.validateAmount(newAmount)} - onBlur={newAmount => { + onChange={newAmount => { + this.validateAmount(newAmount) this.updateGas(newAmount) this.updateAmount(newAmount) }} diff --git a/ui/app/pages/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 bfc0f7e64..41a36e992 100644 --- a/ui/app/pages/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 @@ -8,6 +8,8 @@ import SendRowWrapper from '../../send-row-wrapper/send-row-wrapper.component' import AmountMaxButton from '../amount-max-button/amount-max-button.container' import UserPreferencedTokenInput from '../../../../../components/app/user-preferenced-token-input' +import timeout from '../../../../../../lib/test-timeout' + const propsMethodSpies = { setMaxModeTo: sinon.spy(), updateSendAmount: sinon.spy(), @@ -153,9 +155,8 @@ describe('SendAmountRow Component', function () { assert(wrapper.find(SendRowWrapper).childAt(1).is(UserPreferencedTokenInput)) }) - it('should render the UserPreferencedTokenInput with the correct props', () => { + it('should render the UserPreferencedTokenInput with the correct props', async () => { const { - onBlur, onChange, error, value, @@ -164,8 +165,9 @@ describe('SendAmountRow Component', function () { assert.equal(value, 'mockAmount') assert.equal(SendAmountRow.prototype.updateGas.callCount, 0) assert.equal(SendAmountRow.prototype.updateAmount.callCount, 0) - onBlur('mockNewAmount') - assert.equal(SendAmountRow.prototype.updateGas.callCount, 1) + assert.equal(SendAmountRow.prototype.validateAmount.callCount, 0) + onChange('mockNewAmount') + await timeout(501) assert.deepEqual( SendAmountRow.prototype.updateGas.getCall(0).args, ['mockNewAmount'] @@ -175,8 +177,6 @@ describe('SendAmountRow Component', function () { SendAmountRow.prototype.updateAmount.getCall(0).args, ['mockNewAmount'] ) - assert.equal(SendAmountRow.prototype.validateAmount.callCount, 0) - onChange('mockNewAmount') assert.equal(SendAmountRow.prototype.validateAmount.callCount, 1) assert.deepEqual( SendAmountRow.prototype.validateAmount.getCall(0).args, diff --git a/ui/app/pages/send/send-footer/send-footer.component.js b/ui/app/pages/send/send-footer/send-footer.component.js index f637fb8c7..20ab50c3c 100644 --- a/ui/app/pages/send/send-footer/send-footer.component.js +++ b/ui/app/pages/send/send-footer/send-footer.component.js @@ -28,6 +28,7 @@ export default class SendFooter extends Component { update: PropTypes.func, sendErrors: PropTypes.object, gasEstimateType: PropTypes.string, + gasIsLoading: PropTypes.bool, } static contextTypes = { @@ -102,10 +103,10 @@ export default class SendFooter extends Component { } formShouldBeDisabled () { - const { data, inError, selectedToken, tokenBalance, gasTotal, to, gasLimit } = this.props + const { data, inError, selectedToken, tokenBalance, gasTotal, to, gasLimit, gasIsLoading } = this.props const missingTokenBalance = selectedToken && !tokenBalance const gasLimitTooLow = gasLimit < 5208 // 5208 is hex value of 21000, minimum gas limit - const shouldBeDisabled = inError || !gasTotal || missingTokenBalance || !(data || to) || gasLimitTooLow + const shouldBeDisabled = inError || !gasTotal || missingTokenBalance || !(data || to) || gasLimitTooLow || gasIsLoading return shouldBeDisabled } diff --git a/ui/app/pages/send/send-footer/send-footer.container.js b/ui/app/pages/send/send-footer/send-footer.container.js index 42c12f2d5..52348ca89 100644 --- a/ui/app/pages/send/send-footer/send-footer.container.js +++ b/ui/app/pages/send/send-footer/send-footer.container.js @@ -23,6 +23,7 @@ import { getUnapprovedTxs, getSendErrors, } from '../send.selectors' +import { getGasIsLoading } from '../../../selectors/selectors' import { isSendFormInError, } from './send-footer.selectors' @@ -62,6 +63,7 @@ function mapStateToProps (state) { unapprovedTxs: getUnapprovedTxs(state), sendErrors: getSendErrors(state), gasEstimateType, + gasIsLoading: getGasIsLoading(state), } } diff --git a/ui/app/pages/send/send-footer/tests/send-footer-component.test.js b/ui/app/pages/send/send-footer/tests/send-footer-component.test.js index e1dd0d631..4f35e1b25 100644 --- a/ui/app/pages/send/send-footer/tests/send-footer-component.test.js +++ b/ui/app/pages/send/send-footer/tests/send-footer-component.test.js @@ -81,22 +81,34 @@ describe('SendFooter Component', function () { 'should return true if inError is truthy': { inError: true, expectedResult: true, + gasIsLoading: false, }, 'should return true if gasTotal is falsy': { inError: false, gasTotal: false, expectedResult: true, + gasIsLoading: false, }, 'should return true if to is truthy': { to: '0xsomevalidAddress', inError: false, gasTotal: false, expectedResult: true, + gasIsLoading: false, }, 'should return true if selectedToken is truthy and tokenBalance is falsy': { selectedToken: true, tokenBalance: null, expectedResult: true, + gasIsLoading: false, + }, + 'should return true if gasIsLoading is truthy but all other params are falsy': { + inError: false, + gasTotal: null, + selectedToken: null, + tokenBalance: 0, + expectedResult: true, + gasIsLoading: true, }, 'should return false if inError is false and all other params are truthy': { inError: false, @@ -104,7 +116,9 @@ describe('SendFooter Component', function () { selectedToken: true, tokenBalance: 123, expectedResult: false, + gasIsLoading: false, }, + } Object.entries(config).map(([description, obj]) => { it(description, () => { diff --git a/ui/app/pages/send/send-footer/tests/send-footer-container.test.js b/ui/app/pages/send/send-footer/tests/send-footer-container.test.js index b8af19017..70bc078c8 100644 --- a/ui/app/pages/send/send-footer/tests/send-footer-container.test.js +++ b/ui/app/pages/send/send-footer/tests/send-footer-container.test.js @@ -2,7 +2,6 @@ import assert from 'assert' import proxyquire from 'proxyquire' import sinon from 'sinon' -let mapStateToProps let mapDispatchToProps const actionSpies = { @@ -22,8 +21,7 @@ const utilsStubs = { proxyquire('../send-footer.container.js', { 'react-redux': { - connect: (ms, md) => { - mapStateToProps = ms + connect: (_, md) => { mapDispatchToProps = md return () => ({}) }, @@ -55,30 +53,6 @@ proxyquire('../send-footer.container.js', { describe('send-footer container', () => { - describe('mapStateToProps()', () => { - - it('should map the correct properties to props', () => { - assert.deepEqual(mapStateToProps('mockState'), { - amount: 'mockAmount:mockState', - data: 'mockHexData:mockState', - selectedToken: 'mockSelectedToken:mockState', - editingTransactionId: 'mockEditingTransactionId:mockState', - from: 'mockFromObject:mockState', - gasLimit: 'mockGasLimit:mockState', - gasPrice: 'mockGasPrice:mockState', - gasTotal: 'mockGasTotal:mockState', - inError: 'mockInError:mockState', - to: 'mockTo:mockState', - toAccounts: 'mockToAccounts:mockState', - tokenBalance: 'mockTokenBalance:mockState', - unapprovedTxs: 'mockUnapprovedTxs:mockState', - sendErrors: 'mockSendErrors:mockState', - gasEstimateType: 'mockGasEstimateType:mockState', - }) - }) - - }) - describe('mapDispatchToProps()', () => { let dispatchSpy let mapDispatchToPropsObject From 2ec844e019a76b49d431590ded83c63f7ff58e6d Mon Sep 17 00:00:00 2001 From: Erik Marks <25517051+rekmarks@users.noreply.github.com> Date: Fri, 10 Jan 2020 06:34:02 -0800 Subject: [PATCH 21/70] Fix batch transaction UX (#7473) * order transactions from oldest to newest in UI * update json-rpc-engine, eth-json-rpc-middleware * update e2e and integration tests --- package.json | 4 +-- test/e2e/metamask-ui.spec.js | 10 +++---- test/integration/lib/confirm-sig-requests.js | 16 +++++----- .../confirm-transaction-base.component.js | 2 +- .../confirm-transaction-base.container.js | 12 ++++---- .../confirm-transaction-switch.container.js | 2 +- .../confirm-transaction.container.js | 2 +- ui/index.js | 2 +- yarn.lock | 30 +++++++++++++++---- 9 files changed, 50 insertions(+), 30 deletions(-) diff --git a/package.json b/package.json index 047911146..924f1a918 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,7 @@ "eth-json-rpc-errors": "^1.1.0", "eth-json-rpc-filters": "^4.1.1", "eth-json-rpc-infura": "^4.0.1", - "eth-json-rpc-middleware": "^4.2.0", + "eth-json-rpc-middleware": "^4.4.0", "eth-keyring-controller": "^5.3.0", "eth-ledger-bridge-keyring": "^0.2.0", "eth-method-registry": "^1.2.0", @@ -117,7 +117,7 @@ "gaba": "^1.9.0", "human-standard-token-abi": "^2.0.0", "jazzicon": "^1.2.0", - "json-rpc-engine": "^5.1.5", + "json-rpc-engine": "^5.1.6", "json-rpc-middleware-stream": "^2.1.1", "jsonschema": "^1.2.4", "lodash.debounce": "^4.0.8", diff --git a/test/e2e/metamask-ui.spec.js b/test/e2e/metamask-ui.spec.js index 38c7ac528..62ef04399 100644 --- a/test/e2e/metamask-ui.spec.js +++ b/test/e2e/metamask-ui.spec.js @@ -553,11 +553,11 @@ describe('MetaMask', function () { await delay(regularDelayMs) let transactions = await findElements(driver, By.css('.transaction-list-item')) - await transactions[3].click() + await transactions[0].click() await delay(regularDelayMs) try { transactions = await findElements(driver, By.css('.transaction-list-item'), 1000) - await transactions[3].click() + await transactions[0].click() } catch (e) { console.log(e) } @@ -631,7 +631,7 @@ describe('MetaMask', function () { navigationElement = await findElement(driver, By.css('.confirm-page-container-navigation')) navigationText = await navigationElement.getText() - assert.equal(navigationText.includes('3'), true, 'correct transaction in focus') + assert.equal(navigationText.includes('2'), true, 'correct (same) transaction in focus') }) it('confirms a transaction', async () => { @@ -838,9 +838,9 @@ describe('MetaMask', function () { it('renders the correct ETH balance', async () => { const balance = await findElement(driver, By.css('.transaction-view-balance__primary-balance')) await delay(regularDelayMs) - await driver.wait(until.elementTextMatches(balance, /^87.*\s*ETH.*$/), 10000) + await driver.wait(until.elementTextMatches(balance, /^90.*\s*ETH.*$/), 10000) const tokenAmount = await balance.getText() - assert.ok(/^87.*\s*ETH.*$/.test(tokenAmount)) + assert.ok(/^90.*\s*ETH.*$/.test(tokenAmount)) await delay(regularDelayMs) }) }) diff --git a/test/integration/lib/confirm-sig-requests.js b/test/integration/lib/confirm-sig-requests.js index 65cec89e7..cc32872f4 100644 --- a/test/integration/lib/confirm-sig-requests.js +++ b/test/integration/lib/confirm-sig-requests.js @@ -38,8 +38,8 @@ async function runConfirmSigRequestsTest (assert) { const pendingRequestItem = $.find('.transaction-list-item .transaction-list-item__grid') - if (pendingRequestItem[2]) { - pendingRequestItem[2].click() + if (pendingRequestItem[0]) { + pendingRequestItem[0].click() } await timeout(1000) @@ -47,11 +47,9 @@ async function runConfirmSigRequestsTest (assert) { let confirmSigHeadline = await queryAsync($, '.request-signature__headline') assert.equal(confirmSigHeadline[0].textContent, 'Your signature is being requested') - const confirmSigMessage = await queryAsync($, '.request-signature__notice') - assert.ok(confirmSigMessage[0].textContent.match(/^Signing\sthis\smessage/)) - let confirmSigRowValue = await queryAsync($, '.request-signature__row-value') - assert.equal(confirmSigRowValue[0].textContent, '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0') + assert.equal(confirmSigRowValue[0].textContent, 'Hi, Alice!') + assert.equal(confirmSigRowValue[1].textContent, '1337') let confirmSigSignButton = await queryAsync($, 'button.btn-secondary.btn--large') confirmSigSignButton[0].click() @@ -68,9 +66,11 @@ async function runConfirmSigRequestsTest (assert) { confirmSigHeadline = await queryAsync($, '.request-signature__headline') assert.equal(confirmSigHeadline[0].textContent, 'Your signature is being requested') + const confirmSigMessage = await queryAsync($, '.request-signature__notice') + assert.ok(confirmSigMessage[0].textContent.match(/^Signing\sthis\smessage/)) + confirmSigRowValue = await queryAsync($, '.request-signature__row-value') - assert.equal(confirmSigRowValue[0].textContent, 'Hi, Alice!') - assert.equal(confirmSigRowValue[1].textContent, '1337') + assert.equal(confirmSigRowValue[0].textContent, '0x879a053d4800c6354e76c7985a865d2922c82fb5b3f4577b2fe08b998954f2e0') confirmSigSignButton = await queryAsync($, 'button.btn-secondary.btn--large') confirmSigSignButton[0].click() 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 44b49d697..f67daca62 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 @@ -557,7 +557,7 @@ export default class ConfirmTransactionBase extends Component { getNavigateTxData () { const { currentNetworkUnapprovedTxs, txData: { id } = {} } = this.props - const enumUnapprovedTxs = Object.keys(currentNetworkUnapprovedTxs).reverse() + const enumUnapprovedTxs = Object.keys(currentNetworkUnapprovedTxs) const currentPosition = enumUnapprovedTxs.indexOf(id ? id.toString() : '') return { 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 9a238e780..139edbc02 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 @@ -1,7 +1,6 @@ import { connect } from 'react-redux' import { compose } from 'recompose' import { withRouter } from 'react-router-dom' -import R from 'ramda' import contractMap from 'eth-contract-metadata' import ConfirmTransactionBase from './confirm-transaction-base.component' import { @@ -72,7 +71,9 @@ const mapStateToProps = (state, ownProps) => { nonce, } = confirmTransaction const { txParams = {}, lastGasPrice, id: transactionId, transactionCategory } = txData - const transaction = R.find(({ id }) => id === (transactionId || Number(paramsTransactionId)))(selectedAddressTxList) || {} + const transaction = Object.values(selectedAddressTxList).find( + ({ id }) => id === (transactionId || Number(paramsTransactionId)) + ) || {} const { from: fromAddress, to: txParamsToAddress, @@ -118,10 +119,9 @@ const mapStateToProps = (state, ownProps) => { txData.simulationFails = transaction.simulationFails } - const currentNetworkUnapprovedTxs = R.filter( - ({ metamaskNetworkId }) => metamaskNetworkId === network, - unapprovedTxs, - ) + const currentNetworkUnapprovedTxs = Object.keys(unapprovedTxs) + .filter(key => unapprovedTxs[key].metamaskNetworkId === network) + .reduce((acc, key) => ({ ...acc, [key]: unapprovedTxs[key] }), {}) const unapprovedTxCount = valuesFor(currentNetworkUnapprovedTxs).length const insufficientBalance = !isBalanceSufficient({ diff --git a/ui/app/pages/confirm-transaction-switch/confirm-transaction-switch.container.js b/ui/app/pages/confirm-transaction-switch/confirm-transaction-switch.container.js index 230a931ad..613507e63 100644 --- a/ui/app/pages/confirm-transaction-switch/confirm-transaction-switch.container.js +++ b/ui/app/pages/confirm-transaction-switch/confirm-transaction-switch.container.js @@ -18,7 +18,7 @@ const mapStateToProps = (state, ownProps) => { const unconfirmedTransactions = unconfirmedTransactionsListSelector(state) const totalUnconfirmed = unconfirmedTransactions.length const transaction = totalUnconfirmed - ? unapprovedTxs[transactionId] || unconfirmedTransactions[totalUnconfirmed - 1] + ? unapprovedTxs[transactionId] || unconfirmedTransactions[0] : {} return { diff --git a/ui/app/pages/confirm-transaction/confirm-transaction.container.js b/ui/app/pages/confirm-transaction/confirm-transaction.container.js index 7c3986441..9a45c6405 100644 --- a/ui/app/pages/confirm-transaction/confirm-transaction.container.js +++ b/ui/app/pages/confirm-transaction/confirm-transaction.container.js @@ -35,7 +35,7 @@ const mapStateToProps = (state, ownProps) => { const unconfirmedTransactions = unconfirmedTransactionsListSelector(state) const totalUnconfirmed = unconfirmedTransactions.length const transaction = totalUnconfirmed - ? unapprovedTxs[id] || unconfirmedTransactions[totalUnconfirmed - 1] + ? unapprovedTxs[id] || unconfirmedTransactions[0] : {} const { id: transactionId, transactionCategory } = transaction diff --git a/ui/index.js b/ui/index.js index 058c09be0..f06b31f6c 100644 --- a/ui/index.js +++ b/ui/index.js @@ -61,7 +61,7 @@ async function startApp (metamaskState, backgroundConnection, opts) { const numberOfUnapprivedTx = unapprovedTxsAll.length if (numberOfUnapprivedTx > 0) { store.dispatch(actions.showConfTxPage({ - id: unapprovedTxsAll[numberOfUnapprivedTx - 1].id, + id: unapprovedTxsAll[0].id, })) } diff --git a/yarn.lock b/yarn.lock index 5e9578a16..c86122dd4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10140,7 +10140,7 @@ eth-json-rpc-middleware@^1.5.0: promise-to-callback "^1.0.0" tape "^4.6.3" -eth-json-rpc-middleware@^4.1.4, eth-json-rpc-middleware@^4.1.5, eth-json-rpc-middleware@^4.2.0: +eth-json-rpc-middleware@^4.1.4, eth-json-rpc-middleware@^4.1.5: version "4.2.0" resolved "https://registry.yarnpkg.com/eth-json-rpc-middleware/-/eth-json-rpc-middleware-4.2.0.tgz#cfb77c5056cb8001548c6c7d54f4af5fce04d489" integrity sha512-90LljqRyJhkg7fOwKunh1lu1Mr5bspXMBDitaTGyGPPNiFTbMrhtfbf9fteYlXRFCbq+aIFWwl/X+P7nkrdkLg== @@ -10160,6 +10160,26 @@ eth-json-rpc-middleware@^4.1.4, eth-json-rpc-middleware@^4.1.5, eth-json-rpc-mid pify "^3.0.0" safe-event-emitter "^1.0.1" +eth-json-rpc-middleware@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/eth-json-rpc-middleware/-/eth-json-rpc-middleware-4.4.0.tgz#ef63b783b48dcbea9c1fe25c79e6ea01510e5877" + integrity sha512-IeOsil/XiHsybJO9nFf86+1+YIqGQWPPfiTEp3WLkpLZhJm97kw6tFM7GttIZXIcwtaO3zEXgY6PWAH1jkB3ag== + dependencies: + btoa "^1.2.1" + clone "^2.1.1" + eth-json-rpc-errors "^1.0.1" + eth-query "^2.1.2" + eth-sig-util "^1.4.2" + ethereumjs-block "^1.6.0" + ethereumjs-tx "^1.3.7" + ethereumjs-util "^5.1.2" + ethereumjs-vm "^2.6.0" + fetch-ponyfill "^4.0.0" + json-rpc-engine "^5.1.3" + json-stable-stringify "^1.0.1" + pify "^3.0.0" + safe-event-emitter "^1.0.1" + eth-keyring-controller@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/eth-keyring-controller/-/eth-keyring-controller-5.3.0.tgz#8d85a67b894360ab7d601222ca71df8ed5f456c6" @@ -15861,10 +15881,10 @@ json-rpc-engine@^3.4.0, json-rpc-engine@^3.6.0: promise-to-callback "^1.0.0" safe-event-emitter "^1.0.1" -json-rpc-engine@^5.1.3, json-rpc-engine@^5.1.5: - version "5.1.5" - resolved "https://registry.yarnpkg.com/json-rpc-engine/-/json-rpc-engine-5.1.5.tgz#a5f9915356ea916d5305716354080723c63dede7" - integrity sha512-HTT9HixG4j8vHYrmJIckgbISW9Q8tCkySv7x7Q8zjMpcw10wSe/dZSQ0w08VkDm3y195K4074UlvD3hxaznvlw== +json-rpc-engine@^5.1.3, json-rpc-engine@^5.1.6: + version "5.1.6" + resolved "https://registry.yarnpkg.com/json-rpc-engine/-/json-rpc-engine-5.1.6.tgz#3823c1e227657ac5f22a36351db5bb76fa70cf38" + integrity sha512-9nDeIIu6o7cvzWRrHNuNi+TiGe+YWOp3ZQkHtpPnQzXuX8Y5ZU2Oot3FDI+DaQyXIqQ6SjtM6rixDOJTjjA8NA== dependencies: async "^2.0.1" eth-json-rpc-errors "^2.0.0" From 05634ab1371b44d749e60625a0f39bab025dc2f1 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Fri, 10 Jan 2020 11:12:02 -0400 Subject: [PATCH 22/70] Update changelog for v7.7.2 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index be3177eea..a21afef24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## Current Develop Branch ## 7.7.2 Fri Jan 10 2020 +- [#7753](https://github.com/MetaMask/metamask-extension/pull/7753): Fix gas estimate for tokens +- [#7473](https://github.com/MetaMask/metamask-extension/pull/7473): Fix transaction order on transaction confirmation screen ## 7.7.1 Wed Dec 04 2019 - [#7488](https://github.com/MetaMask/metamask-extension/pull/7488): Fix text overlap when expanding transaction From ba15cf8f63f7cd967dacbfea6c48d8e3f4ff740b Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Fri, 10 Jan 2020 14:49:33 -0400 Subject: [PATCH 23/70] Re-arrange transaction navigation test actions These tests were updated in #7473 to navigate in a different order, because the transaction order changed. Unfortunately this meant that a second contract deployment was being confirmed, where it was previously being rejected. This updates the test to ensure the same transaction is rejected and confirmed as prior to the change in #7473 --- test/e2e/metamask-ui.spec.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/e2e/metamask-ui.spec.js b/test/e2e/metamask-ui.spec.js index 62ef04399..1ee01595b 100644 --- a/test/e2e/metamask-ui.spec.js +++ b/test/e2e/metamask-ui.spec.js @@ -634,21 +634,21 @@ describe('MetaMask', function () { assert.equal(navigationText.includes('2'), true, 'correct (same) transaction in focus') }) - it('confirms a transaction', async () => { + it('rejects a transaction', async () => { await delay(tinyDelayMs) - const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`), 10000) + const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Reject')]`), 10000) await confirmButton.click() await delay(largeDelayMs * 2) const navigationElement = await findElement(driver, By.css('.confirm-page-container-navigation')) await delay(tinyDelayMs) const navigationText = await navigationElement.getText() - assert.equal(navigationText.includes('4'), true, 'transaction confirmed') + assert.equal(navigationText.includes('4'), true, 'transaction rejected') }) - it('rejects a transaction', async () => { + it('confirms a transaction', async () => { await delay(tinyDelayMs / 2) - const rejectButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Reject')]`), 10000) + const rejectButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`), 10000) await delay(tinyDelayMs / 2) await rejectButton.click() await delay(regularDelayMs) @@ -657,7 +657,7 @@ describe('MetaMask', function () { await delay(tinyDelayMs / 2) const navigationText = await navigationElement.getText() await delay(tinyDelayMs / 2) - assert.equal(navigationText.includes('3'), true, 'transaction rejected') + assert.equal(navigationText.includes('3'), true, 'transaction confirmed') }) it('rejects the rest of the transactions', async () => { @@ -838,9 +838,9 @@ describe('MetaMask', function () { it('renders the correct ETH balance', async () => { const balance = await findElement(driver, By.css('.transaction-view-balance__primary-balance')) await delay(regularDelayMs) - await driver.wait(until.elementTextMatches(balance, /^90.*\s*ETH.*$/), 10000) + await driver.wait(until.elementTextMatches(balance, /^87.*\s*ETH.*$/), 10000) const tokenAmount = await balance.getText() - assert.ok(/^90.*\s*ETH.*$/.test(tokenAmount)) + assert.ok(/^87.*\s*ETH.*$/.test(tokenAmount)) await delay(regularDelayMs) }) }) From a1e8229a6532f9c99ff74cfc5a5909bcbc72f9b9 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Fri, 24 Jan 2020 19:24:42 -0330 Subject: [PATCH 24/70] Update GABA dependency version (#7894) --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 924f1a918..9c9cdd1f4 100644 --- a/package.json +++ b/package.json @@ -114,7 +114,7 @@ "extensionizer": "^1.0.1", "fast-json-patch": "^2.0.4", "fuse.js": "^3.2.0", - "gaba": "^1.9.0", + "gaba": "^1.9.3", "human-standard-token-abi": "^2.0.0", "jazzicon": "^1.2.0", "json-rpc-engine": "^5.1.6", diff --git a/yarn.lock b/yarn.lock index c86122dd4..d404c05f0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12180,10 +12180,10 @@ fuse.js@^3.4.4: resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-3.4.5.tgz#8954fb43f9729bd5dbcb8c08f251db552595a7a6" integrity sha512-s9PGTaQIkT69HaeoTVjwGsLfb8V8ScJLx5XGFcKHg0MqLUH/UZ4EKOtqtXX9k7AFqCGxD1aJmYb8Q5VYDibVRQ== -gaba@^1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/gaba/-/gaba-1.9.0.tgz#ccd9f99c56687b5acd39f9e3ceb435b2a59b6aa1" - integrity sha512-HoVreAdZssL0jNHuzZ7WP+YKZ0riu44jVDWxhQ9hsgPuzxbVEsz9fO/HDxqAdNZS1Cswayq6+ciZ3HSCFWMKbQ== +gaba@^1.9.3: + version "1.9.3" + resolved "https://registry.yarnpkg.com/gaba/-/gaba-1.9.3.tgz#4e0e106f3640930f1f06ffe72546903b4c51813e" + integrity sha512-zC9CpaksncAT9SSc4QAxozUE+SKIWN+r9YwhjAJoSeh9joqPJsXlJOHg1/CrHABpvN68QdE00wAYSabYM02EqQ== dependencies: await-semaphore "^0.1.3" eth-contract-metadata "^1.11.0" From ec3a4a79c633488dde36b8ee85f9d8daa18f430e Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Sat, 25 Jan 2020 01:14:29 -0330 Subject: [PATCH 25/70] Use eth-contract-metadata@1.12.1 (#7901) --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 9c9cdd1f4..19ee54737 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "dnode": "^1.2.2", "end-of-stream": "^1.1.0", "eth-block-tracker": "^4.4.2", - "eth-contract-metadata": "^1.11.0", + "eth-contract-metadata": "^1.12.1", "eth-ens-namehash": "^2.0.8", "eth-json-rpc-errors": "^1.1.0", "eth-json-rpc-filters": "^4.1.1", diff --git a/yarn.lock b/yarn.lock index d404c05f0..74d5eb8f6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10038,10 +10038,10 @@ eth-block-tracker@^4.4.2: pify "^3.0.0" safe-event-emitter "^1.0.1" -eth-contract-metadata@^1.11.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/eth-contract-metadata/-/eth-contract-metadata-1.11.0.tgz#4d23a8208d5d53be9d4c0696ed8492b505c6bca1" - integrity sha512-Bbvio71M+lH+qXd8XXddpTc8hhjL9m4fNPOxmZFIX8z0/VooUdwV8YmmDAbkU5WVioZi+Jp1XaoO7VwzXnDboA== +eth-contract-metadata@^1.11.0, eth-contract-metadata@^1.12.1: + version "1.12.1" + resolved "https://registry.yarnpkg.com/eth-contract-metadata/-/eth-contract-metadata-1.12.1.tgz#41014c8c0123453cee15acbcc14299c4d470c759" + integrity sha512-9u2jUcdxaKIv4RvA9RtjyD4+M2yWt4yCulR5bpdQTiG3HUFnN9lHtNL5NIRDpvQVJKerFhexrgEM2WdGP3a6VA== eth-ens-namehash@2.0.8, eth-ens-namehash@^2.0.8: version "2.0.8" From 7bb8b18999f49faace0136a5cdfe63f7bdc8d18e Mon Sep 17 00:00:00 2001 From: ryanml Date: Mon, 27 Jan 2020 04:54:21 -0800 Subject: [PATCH 26/70] Fixing broken JSON import help link (#7910) --- ui/app/pages/create-account/import-account/json.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/pages/create-account/import-account/json.js b/ui/app/pages/create-account/import-account/json.js index 2482e49d1..b0fb63dbf 100644 --- a/ui/app/pages/create-account/import-account/json.js +++ b/ui/app/pages/create-account/import-account/json.js @@ -10,7 +10,7 @@ const { DEFAULT_ROUTE } = require('../../../helpers/constants/routes') const { getMetaMaskAccounts } = require('../../../selectors/selectors') import Button from '../../../components/ui/button' -const HELP_LINK = 'https://metamask.zendesk.com/hc/en-us/articles/360015489351-Importing-Accounts' +const HELP_LINK = 'https://metamask.zendesk.com/hc/en-us/articles/360015489331-Importing-an-Account' class JsonImportSubview extends Component { constructor (props) { From 8787b161e8efdd6250869781d299399361d7d184 Mon Sep 17 00:00:00 2001 From: MetaMask Bot Date: Fri, 24 Jan 2020 22:56:19 +0000 Subject: [PATCH 27/70] Version v7.7.3 --- CHANGELOG.md | 2 ++ app/manifest.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a21afef24..e67452009 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Develop Branch +## 7.7.3 Fri Jan 24 2020 + ## 7.7.2 Fri Jan 10 2020 - [#7753](https://github.com/MetaMask/metamask-extension/pull/7753): Fix gas estimate for tokens - [#7473](https://github.com/MetaMask/metamask-extension/pull/7473): Fix transaction order on transaction confirmation screen diff --git a/app/manifest.json b/app/manifest.json index 7818b046b..b646d3568 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "short_name": "__MSG_appName__", - "version": "7.7.2", + "version": "7.7.3", "manifest_version": 2, "author": "https://metamask.io", "description": "__MSG_appDescription__", From 1b52cccf07a455525d938d2d34992d9c7987a692 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Fri, 24 Jan 2020 19:51:21 -0400 Subject: [PATCH 28/70] Update changelog for v7.7.3 --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e67452009..bcdfe7e04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ ## Current Develop Branch ## 7.7.3 Fri Jan 24 2020 +- [#7894](https://github.com/MetaMask/metamask-extension/pull/7894): Update GABA dependency version +- [#7901](https://github.com/MetaMask/metamask-extension/pull/7901): Use eth-contract-metadata@1.12.1 +- [#7910](https://github.com/MetaMask/metamask-extension/pull/7910): Fixing broken JSON import help link ## 7.7.2 Fri Jan 10 2020 - [#7753](https://github.com/MetaMask/metamask-extension/pull/7753): Fix gas estimate for tokens From 3bce9dec945542d89af85a2f50c6cdce5302b84e Mon Sep 17 00:00:00 2001 From: MetaMask Bot Date: Wed, 29 Jan 2020 04:42:52 +0000 Subject: [PATCH 29/70] Version v7.7.4 --- CHANGELOG.md | 2 ++ app/manifest.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bcdfe7e04..bd9e2fe94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Develop Branch +## 7.7.4 Wed Jan 29 2020 + ## 7.7.3 Fri Jan 24 2020 - [#7894](https://github.com/MetaMask/metamask-extension/pull/7894): Update GABA dependency version - [#7901](https://github.com/MetaMask/metamask-extension/pull/7901): Use eth-contract-metadata@1.12.1 diff --git a/app/manifest.json b/app/manifest.json index b646d3568..2c6cbc644 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "short_name": "__MSG_appName__", - "version": "7.7.3", + "version": "7.7.4", "manifest_version": 2, "author": "https://metamask.io", "description": "__MSG_appDescription__", From acf1f704eacde5a439d12a890211403a4761c674 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Tue, 28 Jan 2020 09:42:07 -0400 Subject: [PATCH 30/70] Update data on Approve screen after updating custom spend limit (#7918) After updating the custom spend limit on the approve screen, the data for the transaction was not being updated. Instead it showed the original transaction data. The transaction data was being updated correctly in the final transaction though. The approve screen has been updated to ensure changes to the custom spend limit are reflected correctly in the data shown. --- .../confirm-approve/confirm-approve.component.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ui/app/pages/confirm-approve/confirm-approve.component.js b/ui/app/pages/confirm-approve/confirm-approve.component.js index e8c44cd4f..3408a6ab8 100644 --- a/ui/app/pages/confirm-approve/confirm-approve.component.js +++ b/ui/app/pages/confirm-approve/confirm-approve.component.js @@ -75,6 +75,10 @@ export default class ConfirmApprove extends Component { ? Number(calcTokenAmount(tokenTrackerBalance, decimals)).toPrecision(9) : '' + const customData = customPermissionAmount + ? getCustomTxParamsData(data, { customPermissionAmount, tokenAmount, decimals }) + : null + return ( showCustomizeGasModal(txData)} showEditApprovalPermissionModal={showEditApprovalPermissionModal} - data={data} + data={customData || data} toAddress={toAddress} currentCurrency={currentCurrency} ethTransactionTotal={ethTransactionTotal} fiatTransactionTotal={fiatTransactionTotal} />} hideSenderToRecipient - customTxParamsData={customPermissionAmount - ? getCustomTxParamsData(data, { customPermissionAmount, tokenAmount, decimals }) - : null - } + customTxParamsData={customData} {...restProps} /> ) From 3bd5d714f71668f539ce98e31b08f0f57c20a969 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Tue, 28 Jan 2020 22:49:32 -0400 Subject: [PATCH 31/70] Allow editing max spend limit (#7919) In the case where the initial spend limit for the `approve` function was set to the maximum amount, editing this value would result in the new limit being silently ignored. The transaction would be submitted with the original max spend limit. This occurred because function to generate the new custom data would look for the expected spend limit in the existing data, then bail if it was not found (and in these cases, it was never found). The reason the value was not found is that it was erroneously being converted to a `Number`. A JavaScript `Number` is not precise enough to represent larger spend limits, so it would give the wrong hex value (after rounding had taken place in the conversion to a floating-point number). The data string is now updated without relying upon the original token value; the new value is inserted after the `spender` argument instead, as the remainder of the `data` string is guaranteed to be the original limit. Additionally, the conversion to a `Number` is now omitted so that the custom spend limit is encoded correctly. Fixes #7915 --- .../edit-approval-permission.component.js | 9 ++-- .../confirm-approve-content.component.js | 2 +- .../confirm-approve.component.js | 12 ++--- .../confirm-approve.container.js | 2 +- .../confirm-approve/confirm-approve.util.js | 49 ++++++++++--------- 5 files changed, 40 insertions(+), 34 deletions(-) diff --git a/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.component.js b/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.component.js index 53ff473e4..28f9fb1f9 100644 --- a/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.component.js +++ b/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.component.js @@ -4,6 +4,7 @@ import Modal from '../../modal' import Identicon from '../../../ui/identicon' import TextField from '../../../ui/text-field' import classnames from 'classnames' +import BigNumber from 'bignumber.js' export default class EditApprovalPermission extends PureComponent { static propTypes = { @@ -61,7 +62,7 @@ export default class EditApprovalPermission extends PureComponent {
    { t('balance') }
    - {`${tokenBalance} ${tokenSymbol}`} + {`${Number(tokenBalance).toPrecision(9)} ${tokenSymbol}`}
    @@ -89,7 +90,7 @@ export default class EditApprovalPermission extends PureComponent { 'edit-approval-permission__edit-section__option-label--selected': selectedOptionIsUnlimited, })}> { - tokenAmount < tokenBalance + (new BigNumber(tokenAmount)).lessThan(new BigNumber(tokenBalance)) ? t('proposedApprovalLimit') : t('unlimited') } @@ -98,7 +99,7 @@ export default class EditApprovalPermission extends PureComponent { { t('spendLimitRequestedBy', [origin]) }
    - {`${tokenAmount} ${tokenSymbol}`} + {`${Number(tokenAmount)} ${tokenSymbol}`}
    @@ -128,7 +129,7 @@ export default class EditApprovalPermission extends PureComponent { { this.setState({ customSpendLimit: event.target.value }) if (selectedOptionIsUnlimited) { diff --git a/ui/app/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js b/ui/app/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js index 38644541d..d1acc6681 100644 --- a/ui/app/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js +++ b/ui/app/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js @@ -100,7 +100,7 @@ export default class ConfirmApproveContent extends Component {
    { t('accessAndSpendNotice', [origin]) }
    { t('amountWithColon') }
    -
    { `${customTokenAmount || tokenAmount} ${tokenSymbol}` }
    +
    { `${Number(customTokenAmount || tokenAmount)} ${tokenSymbol}` }
    { t('toWithColon') }
    diff --git a/ui/app/pages/confirm-approve/confirm-approve.component.js b/ui/app/pages/confirm-approve/confirm-approve.component.js index 3408a6ab8..2e02f6ba3 100644 --- a/ui/app/pages/confirm-approve/confirm-approve.component.js +++ b/ui/app/pages/confirm-approve/confirm-approve.component.js @@ -15,7 +15,7 @@ export default class ConfirmApprove extends Component { static propTypes = { tokenAddress: PropTypes.string, toAddress: PropTypes.string, - tokenAmount: PropTypes.number, + tokenAmount: PropTypes.string, tokenSymbol: PropTypes.string, fiatTransactionTotal: PropTypes.string, ethTransactionTotal: PropTypes.string, @@ -33,7 +33,7 @@ export default class ConfirmApprove extends Component { } static defaultProps = { - tokenAmount: 0, + tokenAmount: '0', } state = { @@ -69,14 +69,14 @@ export default class ConfirmApprove extends Component { } = this.props const { customPermissionAmount } = this.state - const tokensText = `${tokenAmount} ${tokenSymbol}` + const tokensText = `${Number(tokenAmount)} ${tokenSymbol}` const tokenBalance = tokenTrackerBalance - ? Number(calcTokenAmount(tokenTrackerBalance, decimals)).toPrecision(9) + ? calcTokenAmount(tokenTrackerBalance, decimals).toString(10) : '' const customData = customPermissionAmount - ? getCustomTxParamsData(data, { customPermissionAmount, tokenAmount, decimals }) + ? getCustomTxParamsData(data, { customPermissionAmount, decimals }) : null return ( @@ -92,7 +92,7 @@ export default class ConfirmApprove extends Component { this.setState({ customPermissionAmount: newAmount }) }} customTokenAmount={String(customPermissionAmount)} - tokenAmount={String(tokenAmount)} + tokenAmount={tokenAmount} origin={origin} tokenSymbol={tokenSymbol} tokenBalance={tokenBalance} diff --git a/ui/app/pages/confirm-approve/confirm-approve.container.js b/ui/app/pages/confirm-approve/confirm-approve.container.js index 43f5aab90..5a3223564 100644 --- a/ui/app/pages/confirm-approve/confirm-approve.container.js +++ b/ui/app/pages/confirm-approve/confirm-approve.container.js @@ -43,7 +43,7 @@ const mapStateToProps = (state, ownProps) => { const tokenData = getTokenData(data) const tokenValue = tokenData && getTokenValue(tokenData.params) const toAddress = tokenData && getTokenToAddress(tokenData.params) - const tokenAmount = tokenData && calcTokenAmount(tokenValue, decimals).toNumber() + const tokenAmount = tokenData && calcTokenAmount(tokenValue, decimals).toString(10) const contractExchangeRate = contractExchangeRateSelector(state) const { origin } = transaction diff --git a/ui/app/pages/confirm-approve/confirm-approve.util.js b/ui/app/pages/confirm-approve/confirm-approve.util.js index be77c65f9..0318c6bed 100644 --- a/ui/app/pages/confirm-approve/confirm-approve.util.js +++ b/ui/app/pages/confirm-approve/confirm-approve.util.js @@ -1,28 +1,33 @@ import { decimalToHex } from '../../helpers/utils/conversions.util' import { calcTokenValue } from '../../helpers/utils/token-util.js' +import { getTokenData } from '../../helpers/utils/transactions.util' -export function getCustomTxParamsData (data, { customPermissionAmount, tokenAmount, decimals }) { - if (customPermissionAmount) { - const tokenValue = decimalToHex(calcTokenValue(tokenAmount, decimals)) +export function getCustomTxParamsData (data, { customPermissionAmount, decimals }) { + const tokenData = getTokenData(data) - const re = new RegExp('(^.+)' + tokenValue + '$') - const matches = re.exec(data) - - if (!matches || !matches[1]) { - return data - } - let dataWithoutCurrentAmount = matches[1] - const customPermissionValue = decimalToHex(calcTokenValue(Number(customPermissionAmount), decimals)) - - const differenceInLengths = customPermissionValue.length - tokenValue.length - const zeroModifier = dataWithoutCurrentAmount.length - differenceInLengths - if (differenceInLengths > 0) { - dataWithoutCurrentAmount = dataWithoutCurrentAmount.slice(0, zeroModifier) - } else if (differenceInLengths < 0) { - dataWithoutCurrentAmount = dataWithoutCurrentAmount.padEnd(zeroModifier, 0) - } - - const customTxParamsData = dataWithoutCurrentAmount + customPermissionValue - return customTxParamsData + if (!tokenData) { + throw new Error('Invalid data') + } else if (tokenData.name !== 'approve') { + throw new Error(`Invalid data; should be 'approve' method, but instead is '${tokenData.name}'`) } + let spender = tokenData.params[0].value + if (spender.startsWith('0x')) { + spender = spender.substring(2) + } + const [signature, tokenValue] = data.split(spender) + + if (!signature || !tokenValue) { + throw new Error('Invalid data') + } else if (tokenValue.length !== 64) { + throw new Error('Invalid token value; should be exactly 64 hex digits long (u256)') + } + + let customPermissionValue = decimalToHex(calcTokenValue(customPermissionAmount, decimals)) + if (customPermissionValue.length > 64) { + throw new Error('Custom value is larger than u256') + } + + customPermissionValue = customPermissionValue.padStart(tokenValue.length, '0') + const customTxParamsData = `${signature}${spender}${customPermissionValue}` + return customTxParamsData } From c2499d63aae44239679219024f866139397b28f2 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Wed, 29 Jan 2020 14:16:38 -0400 Subject: [PATCH 32/70] Validate custom spend limit (#7920) The custom spend limit was previously not validated. It did have a minimum of zero set, but this didn't have any affect (that minimum is used for form constraint validation, and this field wasn't in a form). The field was never checked to ensure the contents didn't exceed the maximum. The field is now checked for values that exceed the maximum, and invalid values in general (including negative values). The parameters to the `showEditApprovalPermissionModal` were also alphabetized to make them easier to read. In the course of doing this, I noticed that the origin was missing from one of the calls. This was responsible for the modal saying "Spend limit requested by undefined" when clicking "Edit" under the transaction details. This has been fixed. --- app/_locales/en/messages.json | 6 +++ .../edit-approval-permission.component.js | 49 +++++++++++++++++-- .../confirm-approve-content.component.js | 16 +++++- .../confirm-approve.component.js | 1 + .../confirm-approve.container.js | 18 ++++--- 5 files changed, 75 insertions(+), 15 deletions(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index f947c9e5c..c9e1b02db 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -1261,6 +1261,12 @@ "message": "Spend limit requested by $1", "description": "Origin of the site requesting the spend limit" }, + "spendLimitTooLarge": { + "message": "Spend limit too large" + }, + "spendLimitInvalid": { + "message": "Spend limit invalid; must be a positive number" + }, "switchNetworks": { "message": "Switch Networks" }, diff --git a/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.component.js b/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.component.js index 28f9fb1f9..f627ddaef 100644 --- a/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.component.js +++ b/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.component.js @@ -1,13 +1,18 @@ import React, { PureComponent } from 'react' import PropTypes from 'prop-types' +import log from 'loglevel' import Modal from '../../modal' import Identicon from '../../../ui/identicon' import TextField from '../../../ui/text-field' +import { calcTokenAmount } from '../../../../helpers/utils/token-util' import classnames from 'classnames' import BigNumber from 'bignumber.js' +const MAX_UNSIGNED_256_INT = new BigNumber(2).pow(256).minus(1).toString(10) + export default class EditApprovalPermission extends PureComponent { static propTypes = { + decimals: PropTypes.number, hideModal: PropTypes.func.isRequired, selectedIdentity: PropTypes.object, tokenAmount: PropTypes.string, @@ -15,7 +20,7 @@ export default class EditApprovalPermission extends PureComponent { tokenSymbol: PropTypes.string, tokenBalance: PropTypes.string, setCustomAmount: PropTypes.func, - origin: PropTypes.string, + origin: PropTypes.string.isRequired, } static contextTypes = { @@ -27,7 +32,7 @@ export default class EditApprovalPermission extends PureComponent { selectedOptionIsUnlimited: !this.props.customTokenAmount, } - renderModalContent () { + renderModalContent (error) { const { t } = this.context const { hideModal, @@ -128,7 +133,6 @@ export default class EditApprovalPermission extends PureComponent {
    { this.setState({ customSpendLimit: event.target.value }) @@ -139,6 +143,7 @@ export default class EditApprovalPermission extends PureComponent { fullWidth margin="dense" value={ this.state.customSpendLimit } + error={error} />
    @@ -148,10 +153,44 @@ export default class EditApprovalPermission extends PureComponent { ) } + validateSpendLimit () { + const { t } = this.context + const { decimals } = this.props + const { selectedOptionIsUnlimited, customSpendLimit } = this.state + + if (selectedOptionIsUnlimited || !customSpendLimit) { + return + } + + let customSpendLimitNumber + try { + customSpendLimitNumber = new BigNumber(customSpendLimit) + } catch (error) { + log.debug(`Error converting '${customSpendLimit}' to BigNumber:`, error) + return t('spendLimitInvalid') + } + + if (customSpendLimitNumber.isNegative()) { + return t('spendLimitInvalid') + } + + const maxTokenAmount = calcTokenAmount(MAX_UNSIGNED_256_INT, decimals) + if (customSpendLimitNumber.greaterThan(maxTokenAmount)) { + return t('spendLimitTooLarge') + } + } + render () { const { t } = this.context const { setCustomAmount, hideModal, customTokenAmount } = this.props const { selectedOptionIsUnlimited, customSpendLimit } = this.state + + const error = this.validateSpendLimit() + const disabled = Boolean( + (customSpendLimit === customTokenAmount && !selectedOptionIsUnlimited) || + error + ) + return ( { @@ -162,9 +201,9 @@ export default class EditApprovalPermission extends PureComponent { submitType="primary" contentClass="edit-approval-permission-modal-content" containerClass="edit-approval-permission-modal-container" - submitDisabled={ (customSpendLimit === customTokenAmount) && !selectedOptionIsUnlimited } + submitDisabled={disabled} > - { this.renderModalContent() } + { this.renderModalContent(error) } ) } diff --git a/ui/app/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js b/ui/app/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js index d1acc6681..75608c16c 100644 --- a/ui/app/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js +++ b/ui/app/pages/confirm-approve/confirm-approve-content/confirm-approve-content.component.js @@ -15,6 +15,7 @@ export default class ConfirmApproveContent extends Component { static propTypes = { amount: PropTypes.string, txFeeTotal: PropTypes.string, + decimals: PropTypes.number, tokenAmount: PropTypes.string, customTokenAmount: PropTypes.string, tokenSymbol: PropTypes.string, @@ -124,6 +125,7 @@ export default class ConfirmApproveContent extends Component { render () { const { t } = this.context const { + decimals, siteImage, tokenAmount, customTokenAmount, @@ -159,7 +161,15 @@ export default class ConfirmApproveContent extends Component { >
    showEditApprovalPermissionModal({ customTokenAmount, tokenAmount, tokenSymbol, setCustomAmount, tokenBalance, origin })} + onClick={() => showEditApprovalPermissionModal({ + customTokenAmount, + decimals, + origin, + setCustomAmount, + tokenAmount, + tokenSymbol, + tokenBalance, + })} > { t('editPermission') }
    @@ -201,10 +211,12 @@ export default class ConfirmApproveContent extends Component { showEdit: true, onEditClick: () => showEditApprovalPermissionModal({ customTokenAmount, + decimals, + origin, + setCustomAmount, tokenAmount, tokenSymbol, tokenBalance, - setCustomAmount, }), })}
    diff --git a/ui/app/pages/confirm-approve/confirm-approve.component.js b/ui/app/pages/confirm-approve/confirm-approve.component.js index 2e02f6ba3..786a854e7 100644 --- a/ui/app/pages/confirm-approve/confirm-approve.component.js +++ b/ui/app/pages/confirm-approve/confirm-approve.component.js @@ -86,6 +86,7 @@ export default class ConfirmApprove extends Component { showAccountInHeader title={tokensText} contentComponent={ { diff --git a/ui/app/pages/confirm-approve/confirm-approve.container.js b/ui/app/pages/confirm-approve/confirm-approve.container.js index 5a3223564..185c0f9c6 100644 --- a/ui/app/pages/confirm-approve/confirm-approve.container.js +++ b/ui/app/pages/confirm-approve/confirm-approve.container.js @@ -76,20 +76,22 @@ const mapDispatchToProps = (dispatch) => { return { showCustomizeGasModal: (txData) => dispatch(showModal({ name: 'CUSTOMIZE_GAS', txData })), showEditApprovalPermissionModal: ({ - tokenAmount, customTokenAmount, - tokenSymbol, - tokenBalance, - setCustomAmount, + decimals, origin, + setCustomAmount, + tokenAmount, + tokenBalance, + tokenSymbol, }) => dispatch(showModal({ name: 'EDIT_APPROVAL_PERMISSION', - tokenAmount, customTokenAmount, - tokenSymbol, - tokenBalance, - setCustomAmount, + decimals, origin, + setCustomAmount, + tokenAmount, + tokenBalance, + tokenSymbol, })), } } From 257e581d479fa1aea33991a4c04fbb76441850b3 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Wed, 29 Jan 2020 20:57:14 -0400 Subject: [PATCH 33/70] Only resolve ENS on mainnet (#7944) The ENS resolver will now bail on any network other than mainnet. --- app/scripts/background.js | 5 ++++- app/scripts/lib/ens-ipfs/setup.js | 7 +++++-- app/scripts/metamask-controller.js | 4 ++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/app/scripts/background.js b/app/scripts/background.js index 2639d7703..31c8d1815 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -251,7 +251,10 @@ function setupController (initState, initLangCode) { }) const provider = controller.provider - setupEnsIpfsResolver({ provider }) + setupEnsIpfsResolver({ + getCurrentNetwork: controller.getCurrentNetwork, + provider, + }) // submit rpc requests to mesh-metrics controller.networkController.on('rpc-req', (data) => { diff --git a/app/scripts/lib/ens-ipfs/setup.js b/app/scripts/lib/ens-ipfs/setup.js index 8f19510c7..e4eddd494 100644 --- a/app/scripts/lib/ens-ipfs/setup.js +++ b/app/scripts/lib/ens-ipfs/setup.js @@ -6,7 +6,7 @@ const supportedTopLevelDomains = ['eth'] module.exports = setupEnsIpfsResolver -function setupEnsIpfsResolver ({ provider }) { +function setupEnsIpfsResolver ({ provider, getCurrentNetwork }) { // install listener const urlPatterns = supportedTopLevelDomains.map(tld => `*://*.${tld}/*`) @@ -23,7 +23,10 @@ function setupEnsIpfsResolver ({ provider }) { async function webRequestDidFail (details) { const { tabId, url } = details // ignore requests that are not associated with tabs - if (tabId === -1) return + // only attempt ENS resolution on mainnet + if (tabId === -1 || getCurrentNetwork() !== '1') { + return + } // parse ens name const urlData = urlUtil.parse(url) const { hostname: name, path, search, hash: fragment } = urlData diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 9a43b8a80..15e1d6aa2 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -675,6 +675,10 @@ module.exports = class MetamaskController extends EventEmitter { }) } + getCurrentNetwork = () => { + return this.networkController.store.getState().network + } + /** * Collects all the information that we want to share * with the mobile client for syncing purposes From 09c23072f0fa8c47a714e7e4869b360a372d14f0 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Thu, 30 Jan 2020 12:03:28 -0330 Subject: [PATCH 34/70] Update ENS registry addresses (#7954) --- app/scripts/lib/ens-ipfs/resolver.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/app/scripts/lib/ens-ipfs/resolver.js b/app/scripts/lib/ens-ipfs/resolver.js index a0af263bc..09e7b5b32 100644 --- a/app/scripts/lib/ens-ipfs/resolver.js +++ b/app/scripts/lib/ens-ipfs/resolver.js @@ -52,19 +52,23 @@ function hexValueIsEmpty (value) { return [undefined, null, '0x', '0x0', '0x0000000000000000000000000000000000000000000000000000000000000000'].includes(value) } +/** + * Returns the registry address for the given chain ID + * @param {number} chainId the chain ID + * @returns {string|null} the registry address if known, null otherwise + */ function getRegistryForChainId (chainId) { switch (chainId) { - // mainnet case 1: - return '0x314159265dd8dbb310642f98f50c066173c1259b' - // ropsten + // falls through case 3: - return '0x112234455c3a32fd11230c42e7bccd4a84e02010' - // rinkeby + // falls through case 4: - return '0xe7410170f87102df0055eb195163a03b7f2bff4a' - // goerli + // falls through case 5: - return '0x112234455c3a32fd11230c42e7bccd4a84e02010' + // Mainnet, Ropsten, Rinkeby, and Goerli, respectively, use the same address + return '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e' + default: + return null } } From 7c84b3d30a351711f23e98a777b116108a9eb6f1 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Wed, 29 Jan 2020 00:47:18 -0400 Subject: [PATCH 35/70] Update changelog for v7.7.4 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd9e2fe94..d18f195c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,11 @@ ## Current Develop Branch ## 7.7.4 Wed Jan 29 2020 +- [#7918](https://github.com/MetaMask/metamask-extension/pull/7918): Update data on Approve screen after updating custom spend limit +- [#7919](https://github.com/MetaMask/metamask-extension/pull/7919): Allow editing max spend limit +- [#7920](https://github.com/MetaMask/metamask-extension/pull/7920): Validate custom spend limit +- [#7944](https://github.com/MetaMask/metamask-extension/pull/7944): Only resolve ENS on mainnet +- [#7954](https://github.com/MetaMask/metamask-extension/pull/7954): Update ENS registry addresses ## 7.7.3 Fri Jan 24 2020 - [#7894](https://github.com/MetaMask/metamask-extension/pull/7894): Update GABA dependency version From b3fedbcd4442a1b95609b6f0aa99ac7693b10aa8 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Thu, 30 Jan 2020 18:30:56 -0330 Subject: [PATCH 36/70] Update ethereum-ens-network-map in lockfile (#7959) * Update yarn.lock cross-spawn versions * Use ethereum-ens-network-map@1.0.2 --- yarn.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/yarn.lock b/yarn.lock index 74d5eb8f6..5f03bb146 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7576,7 +7576,7 @@ cross-spawn@^4: lru-cache "^4.0.1" which "^1.2.9" -cross-spawn@^5.0.1, cross-spawn@^5.1.0: +cross-spawn@^5.0.1: version "5.1.0" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk= @@ -10370,9 +10370,9 @@ ethereum-common@^0.0.18: integrity sha1-L9w1dvIykDNYl26znaeDIT/5Uj8= ethereum-ens-network-map@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/ethereum-ens-network-map/-/ethereum-ens-network-map-1.0.0.tgz#43cd7669ce950a789e151001118d4d65f210eeb7" - integrity sha1-Q812ac6VCnieFRABEY1NZfIQ7rc= + version "1.0.2" + resolved "https://registry.yarnpkg.com/ethereum-ens-network-map/-/ethereum-ens-network-map-1.0.2.tgz#4e27bad18dae7bd95d84edbcac2c9e739fc959b9" + integrity sha512-5qwJ5n3YhjSpE6O/WEBXCAb2nagUgyagJ6C0lGUBWC4LjKp/rRzD+pwtDJ6KCiITFEAoX4eIrWOjRy0Sylq5Hg== ethereumjs-abi@0.6.5, ethereumjs-abi@^0.6.4, ethereumjs-abi@^0.6.5: version "0.6.5" From 0d94fbcf5c8da2ce93ba02b95f924a398ffde845 Mon Sep 17 00:00:00 2001 From: MetaMask Bot Date: Fri, 14 Feb 2020 20:31:19 +0000 Subject: [PATCH 37/70] Version v7.7.5 --- CHANGELOG.md | 2 ++ app/manifest.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d18f195c1..8fa6f4170 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Develop Branch +## 7.7.5 Fri Feb 14 2020 + ## 7.7.4 Wed Jan 29 2020 - [#7918](https://github.com/MetaMask/metamask-extension/pull/7918): Update data on Approve screen after updating custom spend limit - [#7919](https://github.com/MetaMask/metamask-extension/pull/7919): Allow editing max spend limit diff --git a/app/manifest.json b/app/manifest.json index 2c6cbc644..070c6fc7e 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "short_name": "__MSG_appName__", - "version": "7.7.4", + "version": "7.7.5", "manifest_version": 2, "author": "https://metamask.io", "description": "__MSG_appDescription__", From 39d944fd271162a7551fcee27e074cd46fb24ad1 Mon Sep 17 00:00:00 2001 From: Dan Finlay <542863+danfinlay@users.noreply.github.com> Date: Fri, 14 Feb 2020 11:27:12 -0800 Subject: [PATCH 38/70] Inline the source text not the binary encoding for inpage script (#8053) --- app/scripts/contentscript.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index db4d5fd63..7b302fcec 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -9,7 +9,7 @@ const ObjectMultiplex = require('obj-multiplex') const extension = require('extensionizer') const PortStream = require('extension-port-stream') -const inpageContent = fs.readFileSync(path.join(__dirname, '..', '..', 'dist', 'chrome', 'inpage.js')).toString() +const inpageContent = fs.readFileSync(path.join(__dirname, '..', '..', 'dist', 'chrome', 'inpage.js'), 'utf8') const inpageSuffix = '//# sourceURL=' + extension.runtime.getURL('inpage.js') + '\n' const inpageBundle = inpageContent + inpageSuffix From bca4594f0af6b36d7d931f384c42d8e8226fb7a0 Mon Sep 17 00:00:00 2001 From: Dan Finlay <542863+danfinlay@users.noreply.github.com> Date: Fri, 14 Feb 2020 12:32:56 -0800 Subject: [PATCH 39/70] Add warning to watchAsset API when editing a known token (#8049) * Add warning when editing a known token with watchAsset API * Add warning when watchAsset attempts to reuse token symbol --- app/_locales/en/messages.json | 6 +++ .../confirm-add-suggested-token.component.js | 47 ++++++++++++++++++- .../confirm-add-suggested-token.container.js | 3 +- 3 files changed, 54 insertions(+), 2 deletions(-) diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index c9e1b02db..73c59555e 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -715,6 +715,12 @@ "knownAddressRecipient": { "message": "Known contract address." }, + "knownTokenWarning": { + "message": "This action will edit tokens that are already listed in your wallet, which can be used to phish you. Only approve if you are certain that you mean to change what these tokens represent." + }, + "reusedTokenNameWarning": { + "message": "A token here reuses a symbol from another token you watch, this can be confusing or deceptive." + }, "invalidAddressRecipientNotEthNetwork": { "message": "Not ETH network, set to lowercase" }, 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 04e9c8dcf..7430933b3 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 @@ -16,6 +16,7 @@ export default class ConfirmAddSuggestedToken extends Component { addToken: PropTypes.func, pendingTokens: PropTypes.object, removeSuggestedTokens: PropTypes.func, + tokens: PropTypes.array, } componentDidMount () { @@ -33,9 +34,11 @@ export default class ConfirmAddSuggestedToken extends Component { } render () { - const { addToken, pendingTokens, removeSuggestedTokens, history } = this.props + const { addToken, pendingTokens, tokens, removeSuggestedTokens, history } = this.props const pendingTokenKey = Object.keys(pendingTokens)[0] const pendingToken = pendingTokens[pendingTokenKey] + const hasTokenDuplicates = this.checkTokenDuplicates(pendingTokens, tokens) + const reusesName = this.checkNameReuse(pendingTokens, tokens) return (
    @@ -46,6 +49,20 @@ export default class ConfirmAddSuggestedToken extends Component {
    { this.context.t('likeToAddTokens') }
    + { hasTokenDuplicates ? + ( +
    + { this.context.t('knownTokenWarning') } +
    + ) : null + } + { reusesName ? + ( +
    + { this.context.t('reusedTokenNameWarning') } +
    + ) : null + }
    @@ -119,4 +136,32 @@ export default class ConfirmAddSuggestedToken extends Component {
    ) } + + checkTokenDuplicates (pendingTokens, tokens) { + const pending = Object.keys(pendingTokens) + const existing = tokens.map(token => token.address) + const dupes = pending.filter((proposed) => { + return existing.includes(proposed) + }) + + return dupes.length > 0 + } + + /** + * Returns true if any pendingTokens both: + * - Share a symbol with an existing `tokens` member. + * - Does not share an address with that same `tokens` member. + * This should be flagged as possibly deceptive or confusing. + */ + checkNameReuse (pendingTokens, tokens) { + const duplicates = Object.keys(pendingTokens) + .map((addr) => pendingTokens[addr]) + .filter((token) => { + const dupes = tokens.filter(old => old.symbol === token.symbol) + .filter(old => old.address !== token.address) + return dupes.length > 0 + }) + return duplicates.length > 0 + } + } 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 cc73b2ea7..63932ffa5 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 @@ -8,11 +8,12 @@ const extend = require('xtend') const { addToken, removeSuggestedTokens } = require('../../store/actions') const mapStateToProps = ({ metamask }) => { - const { pendingTokens, suggestedTokens } = metamask + const { pendingTokens, suggestedTokens, tokens } = metamask const params = extend(pendingTokens, suggestedTokens) return { pendingTokens: params, + tokens, } } From abdc3c5626c9caceaeb7212655b7eccad918c51a Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Fri, 14 Feb 2020 18:07:24 -0400 Subject: [PATCH 40/70] Update Wyre ETH purchase url (#8051) * Revert "Revert "Update Wyre ETH purchase url" (#7631)" This reverts commit bc67d1eecabb123bf21445e604780d2454d52d3e. * Restrict widget to just debit card payments Apple Pay apparently only works on Safari. --- app/scripts/lib/buy-eth-url.js | 2 +- test/unit/app/buy-eth-url.spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/scripts/lib/buy-eth-url.js b/app/scripts/lib/buy-eth-url.js index 5cae83a9f..36dec6f61 100644 --- a/app/scripts/lib/buy-eth-url.js +++ b/app/scripts/lib/buy-eth-url.js @@ -17,7 +17,7 @@ function getBuyEthUrl ({ network, amount, address, service }) { switch (service) { case 'wyre': - return `https://dash.sendwyre.com/sign-up` + return `https://pay.sendwyre.com/?dest=ethereum:${address}&destCurrency=ETH&accountId=AC-7AG3W4XH4N2&paymentMethod=debit-card` case 'coinswitch': return `https://metamask.coinswitch.co/?address=${address}&to=eth` case 'coinbase': diff --git a/test/unit/app/buy-eth-url.spec.js b/test/unit/app/buy-eth-url.spec.js index 7db992244..3cdad93b5 100644 --- a/test/unit/app/buy-eth-url.spec.js +++ b/test/unit/app/buy-eth-url.spec.js @@ -20,7 +20,7 @@ describe('buy-eth-url', function () { it('returns coinbase url with amount and address for network 1', function () { const wyreUrl = getBuyEthUrl(mainnet) - assert.equal(wyreUrl, 'https://dash.sendwyre.com/sign-up') + assert.equal(wyreUrl, 'https://pay.sendwyre.com/?dest=ethereum:0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc&destCurrency=ETH&accountId=AC-7AG3W4XH4N2&paymentMethod=debit-card') }) From 18c2720c42959f39476f4b72150492884ce5812c Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Mon, 17 Feb 2020 20:33:01 -0400 Subject: [PATCH 41/70] Update packages to address security advisory (#8061) Update packages to address the following security advisories: * https://www.npmjs.com/advisories/1474 * https://www.npmjs.com/advisories/1475 * https://www.npmjs.com/advisories/1476 * https://www.npmjs.com/advisories/1480 * https://www.npmjs.com/advisories/1482 --- yarn.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/yarn.lock b/yarn.lock index 5f03bb146..14bcae586 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1671,7 +1671,7 @@ through2 "^2.0.3" "@hapi/accept@3.x.x": - version "3.2.2" + version "3.2.4" resolved "https://registry.yarnpkg.com/@hapi/accept/-/accept-3.2.2.tgz#3a033af67be8196c23f8f51aa8756687b972c7cb" integrity sha512-UtXlTT59srtMr7ZRBzK2CvyWqFwlf78hPt9jEXqkwfbwiwRH1PRv/qkS8lgr5ZyoG6kfpU3xTgt2X91Yfe/6Yg== dependencies: @@ -1684,7 +1684,7 @@ integrity sha512-mV6T0IYqb0xL1UALPFplXYQmR0twnXG0M6jUswpquqT2sD12BOiCiLy3EvMp/Fy7s3DZElC4/aPjEjo2jeZpvw== "@hapi/ammo@3.x.x", "@hapi/ammo@^3.1.0": - version "3.1.0" + version "3.1.2" resolved "https://registry.yarnpkg.com/@hapi/ammo/-/ammo-3.1.0.tgz#a3281ecb68bf7af2ffa6d0f7656536a7d23700ed" integrity sha512-iFQBEfm3WwWy8JdPQ8l6qXVLPtzmjITVfaxwl6dfoP8kKv6i2Uk43Ax+ShkNfOVyfEnNggqL2IyZTY3DaaRGNg== dependencies: @@ -1763,7 +1763,7 @@ integrity sha512-Bsfp/+1Gyf70eGtnIgmScvrH8sSypO3TcK3Zf0QdHnzn/ACnAkI6KLtGACmNRPEzzIy+W7aJX5E+1fc9GwIABQ== "@hapi/hapi@^18.3.1": - version "18.3.1" + version "18.4.1" resolved "https://registry.yarnpkg.com/@hapi/hapi/-/hapi-18.3.1.tgz#936d1e24551b11486eabd6a4974bea4ec2127669" integrity sha512-gBiU9isWWezrg0ucX95Ph6AY6fUKZub3FxKapaleoFBJDOUcxTYiQR6Lha2zvHalIFoTl3K04O3Yr/5pD17QkQ== dependencies: @@ -1907,7 +1907,7 @@ "@hapi/joi" "15.x.x" "@hapi/subtext@6.x.x": - version "6.1.2" + version "6.1.3" resolved "https://registry.yarnpkg.com/@hapi/subtext/-/subtext-6.1.2.tgz#87754f0c25b4e2676575a3686541e5b555b0c717" integrity sha512-G1kqD1E2QdxpvpL26WieIyo3z0qCa/sAGSa2TJI/PYPWCR9rL0rqFvhWY774xPZ4uK1PV3TIaJcx8AruAvxclg== dependencies: @@ -7576,7 +7576,7 @@ cross-spawn@^4: lru-cache "^4.0.1" which "^1.2.9" -cross-spawn@^5.0.1: +cross-spawn@^5.0.1, cross-spawn@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk= From 5156191ba79398a688babe0ec43fa1cd5d6de742 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Mon, 17 Feb 2020 22:46:17 -0330 Subject: [PATCH 42/70] Update dependencies to resolve security advisories (#8064) --- yarn.lock | 70 +++++++++++++++++++++++++++---------------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/yarn.lock b/yarn.lock index 14bcae586..4e830229d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1670,25 +1670,25 @@ normalize-path "^2.0.1" through2 "^2.0.3" -"@hapi/accept@3.x.x": +"@hapi/accept@^3.2.4": version "3.2.4" - resolved "https://registry.yarnpkg.com/@hapi/accept/-/accept-3.2.2.tgz#3a033af67be8196c23f8f51aa8756687b972c7cb" - integrity sha512-UtXlTT59srtMr7ZRBzK2CvyWqFwlf78hPt9jEXqkwfbwiwRH1PRv/qkS8lgr5ZyoG6kfpU3xTgt2X91Yfe/6Yg== + resolved "https://registry.yarnpkg.com/@hapi/accept/-/accept-3.2.4.tgz#687510529493fe1d7d47954c31aff360d9364bd1" + integrity sha512-soThGB+QMgfxlh0Vzhzlf3ZOEOPk5biEwcOXhkF0Eedqx8VnhGiggL9UYHrIsOb1rUg3Be3K8kp0iDL2wbVSOQ== dependencies: "@hapi/boom" "7.x.x" - "@hapi/hoek" "6.x.x" + "@hapi/hoek" "8.x.x" "@hapi/address@2.x.x": version "2.0.0" resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.0.0.tgz#9f05469c88cb2fd3dcd624776b54ee95c312126a" integrity sha512-mV6T0IYqb0xL1UALPFplXYQmR0twnXG0M6jUswpquqT2sD12BOiCiLy3EvMp/Fy7s3DZElC4/aPjEjo2jeZpvw== -"@hapi/ammo@3.x.x", "@hapi/ammo@^3.1.0": +"@hapi/ammo@3.x.x", "@hapi/ammo@^3.1.0", "@hapi/ammo@^3.1.2": version "3.1.2" - resolved "https://registry.yarnpkg.com/@hapi/ammo/-/ammo-3.1.0.tgz#a3281ecb68bf7af2ffa6d0f7656536a7d23700ed" - integrity sha512-iFQBEfm3WwWy8JdPQ8l6qXVLPtzmjITVfaxwl6dfoP8kKv6i2Uk43Ax+ShkNfOVyfEnNggqL2IyZTY3DaaRGNg== + resolved "https://registry.yarnpkg.com/@hapi/ammo/-/ammo-3.1.2.tgz#a9edf5d48d99b75fdcd7ab3dabf9059942a06961" + integrity sha512-ej9OtFmiZv1qr45g1bxEZNGyaR4jRpyMxU6VhbxjaYThymvOwsyIsUKMZnP5Qw2tfYFuwqCJuIBHGpeIbdX9gQ== dependencies: - "@hapi/hoek" "6.x.x" + "@hapi/hoek" "8.x.x" "@hapi/b64@4.x.x": version "4.2.0" @@ -1717,13 +1717,13 @@ resolved "https://registry.yarnpkg.com/@hapi/bourne/-/bourne-1.3.2.tgz#0a7095adea067243ce3283e1b56b8a8f453b242a" integrity sha512-1dVNHT76Uu5N3eJNTYcvxee+jzX4Z9lfciqRRHCU27ihbUcYi+iSc2iml5Ke1LXe1SyJCLA0+14Jh4tXJgOppA== -"@hapi/call@5.x.x": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@hapi/call/-/call-5.1.0.tgz#91e4c434318003173d96d211d90d35736a33945e" - integrity sha512-CiVEXjD/jiIHBqufBW3pdedshEMjRmHtff7m1puot8j4MUmuKRbLlh0DB8fv6QqH/7/55pH1qgFj300r0WpyMw== +"@hapi/call@^5.1.3": + version "5.1.3" + resolved "https://registry.yarnpkg.com/@hapi/call/-/call-5.1.3.tgz#217af45e3bc3d38b03aa5c9edfe1be939eee3741" + integrity sha512-5DfWpMk7qZiYhvBhM5oUiT4GQ/O8a2rFR121/PdwA/eZ2C1EsuD547ZggMKAR5bZ+FtxOf0fdM20zzcXzq2mZA== dependencies: "@hapi/boom" "7.x.x" - "@hapi/hoek" "6.x.x" + "@hapi/hoek" "8.x.x" "@hapi/catbox-memory@4.x.x": version "4.1.0" @@ -1743,10 +1743,10 @@ "@hapi/joi" "15.x.x" "@hapi/podium" "3.x.x" -"@hapi/content@4.x.x", "@hapi/content@^4.1.0": - version "4.1.0" - resolved "https://registry.yarnpkg.com/@hapi/content/-/content-4.1.0.tgz#5265516949ca081e85a43e97c1058ff53fc69714" - integrity sha512-hv2Czsl49hnWDEfRZOFow/BmYbKyfEknmk3k83gOp6moFn5ceHB4xVcna8OwsGfy8dxO81lhpPy+JgQEaU4SWw== +"@hapi/content@^4.1.0", "@hapi/content@^4.1.1": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@hapi/content/-/content-4.1.1.tgz#179673d1e2b7eb36c564d8f9605d019bd2252cbf" + integrity sha512-3TWvmwpVPxFSF3KBjKZ8yDqIKKZZIm7VurDSweYpXYENZrJH3C1hd1+qEQW9wQaUaI76pPBLGrXl6I3B7i3ipA== dependencies: "@hapi/boom" "7.x.x" @@ -1764,25 +1764,25 @@ "@hapi/hapi@^18.3.1": version "18.4.1" - resolved "https://registry.yarnpkg.com/@hapi/hapi/-/hapi-18.3.1.tgz#936d1e24551b11486eabd6a4974bea4ec2127669" - integrity sha512-gBiU9isWWezrg0ucX95Ph6AY6fUKZub3FxKapaleoFBJDOUcxTYiQR6Lha2zvHalIFoTl3K04O3Yr/5pD17QkQ== + resolved "https://registry.yarnpkg.com/@hapi/hapi/-/hapi-18.4.1.tgz#023fbc131074b1cb2cd7f6766d65f4b0e92df788" + integrity sha512-9HjVGa0Z4Qv9jk9AVoUdJMQLA+KuZ+liKWyEEkVBx3e3H1F0JM6aGbPkY9jRfwsITBWGBU2iXazn65SFKSi/tg== dependencies: - "@hapi/accept" "3.x.x" - "@hapi/ammo" "3.x.x" + "@hapi/accept" "^3.2.4" + "@hapi/ammo" "^3.1.2" "@hapi/boom" "7.x.x" "@hapi/bounce" "1.x.x" - "@hapi/call" "5.x.x" + "@hapi/call" "^5.1.3" "@hapi/catbox" "10.x.x" "@hapi/catbox-memory" "4.x.x" "@hapi/heavy" "6.x.x" - "@hapi/hoek" "6.x.x" + "@hapi/hoek" "8.x.x" "@hapi/joi" "15.x.x" "@hapi/mimos" "4.x.x" "@hapi/podium" "3.x.x" "@hapi/shot" "4.x.x" "@hapi/somever" "2.x.x" "@hapi/statehood" "6.x.x" - "@hapi/subtext" "6.x.x" + "@hapi/subtext" "^6.1.3" "@hapi/teamwork" "3.x.x" "@hapi/topo" "3.x.x" @@ -1858,15 +1858,15 @@ "@hapi/hoek" "6.x.x" "@hapi/vise" "3.x.x" -"@hapi/pez@4.x.x": - version "4.1.0" - resolved "https://registry.yarnpkg.com/@hapi/pez/-/pez-4.1.0.tgz#97a88e5d19e756efbd24e5f583d59fa8db1d6cba" - integrity sha512-c+AxL8/cCj+7FB+tzJ5FhWKYP8zF7/7mA3Ft3a5y7h6YT26qzhj5d2JY27jur30KaZbrZAd4ofXXkqvE/IpJlA== +"@hapi/pez@^4.1.2": + version "4.1.2" + resolved "https://registry.yarnpkg.com/@hapi/pez/-/pez-4.1.2.tgz#14984d0c31fed348f10c962968a21d9761f55503" + integrity sha512-8zSdJ8cZrJLFldTgwjU9Fb1JebID+aBCrCsycgqKYe0OZtM2r3Yv3aAwW5z97VsZWCROC1Vx6Mdn4rujh5Ktcg== dependencies: "@hapi/b64" "4.x.x" "@hapi/boom" "7.x.x" - "@hapi/content" "4.x.x" - "@hapi/hoek" "6.x.x" + "@hapi/content" "^4.1.1" + "@hapi/hoek" "8.x.x" "@hapi/nigel" "3.x.x" "@hapi/podium@3.x.x": @@ -1906,17 +1906,17 @@ "@hapi/iron" "5.x.x" "@hapi/joi" "15.x.x" -"@hapi/subtext@6.x.x": +"@hapi/subtext@^6.1.3": version "6.1.3" - resolved "https://registry.yarnpkg.com/@hapi/subtext/-/subtext-6.1.2.tgz#87754f0c25b4e2676575a3686541e5b555b0c717" - integrity sha512-G1kqD1E2QdxpvpL26WieIyo3z0qCa/sAGSa2TJI/PYPWCR9rL0rqFvhWY774xPZ4uK1PV3TIaJcx8AruAvxclg== + resolved "https://registry.yarnpkg.com/@hapi/subtext/-/subtext-6.1.3.tgz#bbd07771ae2a4e73ac360c93ed74ac641718b9c6" + integrity sha512-qWN6NbiHNzohVcJMeAlpku/vzbyH4zIpnnMPMPioQMwIxbPFKeNViDCNI6fVBbMPBiw/xB4FjqiJkRG5P9eWWg== dependencies: "@hapi/boom" "7.x.x" "@hapi/bourne" "1.x.x" - "@hapi/content" "4.x.x" + "@hapi/content" "^4.1.1" "@hapi/file" "1.x.x" "@hapi/hoek" "8.x.x" - "@hapi/pez" "4.x.x" + "@hapi/pez" "^4.1.2" "@hapi/wreck" "15.x.x" "@hapi/teamwork@3.x.x": From 5b030bfdc387f112167d4cf175f9cfc543c2abc7 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Mon, 17 Feb 2020 21:20:01 -0400 Subject: [PATCH 43/70] Attempt ENS resolution on any valid domain name (#8059) ENS currently supports a variety of tlds in addition to `.eth`, and more will be supported in the future. Rather than hard-code a list of supported ENS tlds, all valid domain names will now be interpreted as potential ENS addresses in our address input component. Closes #7978 --- test/unit/util_test.js | 58 +++++++++++++++++++ ui/app/helpers/utils/util.js | 13 ++++- .../add-recipient/ens-input.component.js | 8 +-- ui/app/pages/send/send.container.js | 6 +- .../add-contact/add-contact.component.js | 6 +- 5 files changed, 78 insertions(+), 13 deletions(-) diff --git a/test/unit/util_test.js b/test/unit/util_test.js index 87f57b218..10bfc6c1a 100644 --- a/test/unit/util_test.js +++ b/test/unit/util_test.js @@ -103,6 +103,64 @@ describe('util', function () { }) }) + describe('isValidDomainName', function () { + it('should return true when given a valid domain name', function () { + assert.strictEqual(util.isValidDomainName('foo.bar'), true) + }) + + it('should return true when given a valid subdomain', function () { + assert.strictEqual(util.isValidDomainName('foo.foo.bar'), true) + }) + + it('should return true when given a single-character domain', function () { + assert.strictEqual(util.isValidDomainName('f.bar'), true) + }) + + it('should return true when given a unicode TLD', function () { + assert.strictEqual(util.isValidDomainName('台灣.中国'), true) + }) + + it('should return false when given a domain with unacceptable ASCII characters', function () { + assert.strictEqual(util.isValidDomainName('$.bar'), false) + }) + + it('should return false when given a TLD that starts with a dash', function () { + assert.strictEqual(util.isValidDomainName('foo.-bar'), false) + }) + + it('should return false when given a TLD that ends with a dash', function () { + assert.strictEqual(util.isValidDomainName('foo.bar-'), false) + }) + + it('should return false when given a domain name with a chunk that starts with a dash', function () { + assert.strictEqual(util.isValidDomainName('-foo.bar'), false) + }) + + it('should return false when given a domain name with a chunk that ends with a dash', function () { + assert.strictEqual(util.isValidDomainName('foo-.bar'), false) + }) + + it('should return false when given a bare TLD', function () { + assert.strictEqual(util.isValidDomainName('bar'), false) + }) + + it('should return false when given a domain that starts with a period', function () { + assert.strictEqual(util.isValidDomainName('.bar'), false) + }) + + it('should return false when given a subdomain that starts with a period', function () { + assert.strictEqual(util.isValidDomainName('.foo.bar'), false) + }) + + it('should return false when given a domain that ends with a period', function () { + assert.strictEqual(util.isValidDomainName('bar.'), false) + }) + + it('should return false when given a 1-character TLD', function () { + assert.strictEqual(util.isValidDomainName('foo.b'), false) + }) + }) + describe('#numericBalance', function () { it('should return a BN 0 if given nothing', function () { var result = util.numericBalance() diff --git a/ui/app/helpers/utils/util.js b/ui/app/helpers/utils/util.js index 19aabb770..90b6fc452 100644 --- a/ui/app/helpers/utils/util.js +++ b/ui/app/helpers/utils/util.js @@ -2,6 +2,7 @@ const abi = require('human-standard-token-abi') const ethUtil = require('ethereumjs-util') const hexToBn = require('../../../../app/scripts/lib/hex-to-bn') import { DateTime } from 'luxon' +import punycode from 'punycode' const MIN_GAS_PRICE_GWEI_BN = new ethUtil.BN(1) const GWEI_FACTOR = new ethUtil.BN(1e9) @@ -36,7 +37,7 @@ module.exports = { miniAddressSummary: miniAddressSummary, isAllOneCase: isAllOneCase, isValidAddress: isValidAddress, - isValidENSAddress, + isValidDomainName, numericBalance: numericBalance, parseBalance: parseBalance, formatBalance: formatBalance, @@ -99,8 +100,14 @@ function isValidAddress (address) { return (isAllOneCase(prefixed) && ethUtil.isValidAddress(prefixed)) || ethUtil.isValidChecksumAddress(prefixed) } -function isValidENSAddress (address) { - return address.match(/^.{3,}\.(eth|test|xyz)$/) +function isValidDomainName (address) { + const match = punycode.toASCII(address) + .toLowerCase() + // Checks that the domain consists of at least one valid domain pieces separated by periods, followed by a tld + // Each piece of domain name has only the characters a-z, 0-9, and a hyphen (but not at the start or end of chunk) + // A chunk has minimum length of 1, but minimum tld is set to 2 for now (no 1-character tlds exist yet) + .match(/^(?:[a-z0-9](?:[-a-z0-9]*[a-z0-9])?\.)+[a-z0-9][-a-z0-9]*[a-z0-9]$/) + return match !== null } function isInvalidChecksumAddress (address) { diff --git a/ui/app/pages/send/send-content/add-recipient/ens-input.component.js b/ui/app/pages/send/send-content/add-recipient/ens-input.component.js index 8f8023fd0..11b9c5cb5 100644 --- a/ui/app/pages/send/send-content/add-recipient/ens-input.component.js +++ b/ui/app/pages/send/send-content/add-recipient/ens-input.component.js @@ -1,8 +1,8 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import c from 'classnames' -import { isValidENSAddress, isValidAddress, isValidAddressHead } from '../../../../helpers/utils/util' -import {ellipsify} from '../../send.utils' +import { isValidDomainName, isValidAddress, isValidAddressHead } from '../../../../helpers/utils/util' +import { ellipsify } from '../../send.utils' import debounce from 'debounce' import copyToClipboard from 'copy-to-clipboard/index' @@ -91,7 +91,7 @@ export default class EnsInput extends Component { this.props.updateEnsResolution(address) }) .catch((reason) => { - if (isValidENSAddress(recipient) && reason.message === 'ENS name not defined.') { + if (isValidDomainName(recipient) && reason.message === 'ENS name not defined.') { this.props.updateEnsResolutionError(this.context.t('ensNotFoundOnCurrentNetwork')) } else { log.error(reason) @@ -124,7 +124,7 @@ export default class EnsInput extends Component { return } - if (isValidENSAddress(input)) { + if (isValidDomainName(input)) { this.lookupEnsName(input) } else if (onValidAddressTyped && isValidAddress(input)) { onValidAddressTyped(input) diff --git a/ui/app/pages/send/send.container.js b/ui/app/pages/send/send.container.js index 2350712f3..f8512a7bb 100644 --- a/ui/app/pages/send/send.container.js +++ b/ui/app/pages/send/send.container.js @@ -55,7 +55,7 @@ import { calcGasTotal, } from './send.utils.js' import { - isValidENSAddress, + isValidDomainName, } from '../../helpers/utils/util' import { @@ -126,8 +126,8 @@ function mapDispatchToProps (dispatch) { updateSendEnsResolution: (ensResolution) => dispatch(updateSendEnsResolution(ensResolution)), updateSendEnsResolutionError: (message) => dispatch(updateSendEnsResolutionError(message)), updateToNicknameIfNecessary: (to, toNickname, addressBook) => { - if (isValidENSAddress(toNickname)) { - const addressBookEntry = addressBook.find(({ address}) => to === address) || {} + if (isValidDomainName(toNickname)) { + const addressBookEntry = addressBook.find(({ address }) => to === address) || {} if (!addressBookEntry.name !== toNickname) { dispatch(updateSendTo(to, addressBookEntry.name || '')) } diff --git a/ui/app/pages/settings/contact-list-tab/add-contact/add-contact.component.js b/ui/app/pages/settings/contact-list-tab/add-contact/add-contact.component.js index 56d4415cf..878ea10c2 100644 --- a/ui/app/pages/settings/contact-list-tab/add-contact/add-contact.component.js +++ b/ui/app/pages/settings/contact-list-tab/add-contact/add-contact.component.js @@ -3,8 +3,8 @@ import PropTypes from 'prop-types' import Identicon from '../../../../components/ui/identicon' import TextField from '../../../../components/ui/text-field' import { CONTACT_LIST_ROUTE } from '../../../../helpers/constants/routes' -import { isValidAddress, isValidENSAddress } from '../../../../helpers/utils/util' -import EnsInput from '../../../../pages/send/send-content/add-recipient/ens-input' +import { isValidAddress, isValidDomainName } from '../../../../helpers/utils/util' +import EnsInput from '../../../send/send-content/add-recipient/ens-input' import PageContainerFooter from '../../../../components/ui/page-container/page-container-footer' import debounce from 'lodash.debounce' @@ -51,7 +51,7 @@ export default class AddContact extends PureComponent { validate = address => { const valid = isValidAddress(address) - const validEnsAddress = isValidENSAddress(address) + const validEnsAddress = isValidDomainName(address) if (valid || validEnsAddress || address === '') { this.setState({ error: '', ethAddress: address }) } else { From f78247b51acb891a9e8fc1657cc5705a201da621 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Fri, 14 Feb 2020 16:45:14 -0400 Subject: [PATCH 44/70] Update changelog for v7.7.5 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fa6f4170..dbaab1961 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ ## Current Develop Branch ## 7.7.5 Fri Feb 14 2020 +- [#8053](https://github.com/MetaMask/metamask-extension/pull/8053): Inline the source text not the binary encoding for inpage script +- [#8049](https://github.com/MetaMask/metamask-extension/pull/8049): Add warning to watchAsset API when editing a known token +- [#8051](https://github.com/MetaMask/metamask-extension/pull/8051): Update Wyre ETH purchase url +- [#8059](https://github.com/MetaMask/metamask-extension/pull/8059): Attempt ENS resolution on any valid domain name ## 7.7.4 Wed Jan 29 2020 - [#7918](https://github.com/MetaMask/metamask-extension/pull/7918): Update data on Approve screen after updating custom spend limit From 9f1f85715ead7121c8b3379c77555040e55c10b8 Mon Sep 17 00:00:00 2001 From: MetaMask Bot <37885440+metamaskbot@users.noreply.github.com> Date: Tue, 3 Mar 2020 07:17:28 -0800 Subject: [PATCH 45/70] Version v7.7.6 RC (#8153) * Version v7.7.6 * Use @metamask/eth-ledger-bridge-keyring@0.2.2 (#8154) * Use @metamask/eth-ledger-bridge-keyring@0.2.2 * Update usages of eth-ledger-bridge-keyring * Update CHANGELOG.md Co-authored-by: Whymarrh Whitby --- CHANGELOG.md | 3 +++ app/manifest.json | 2 +- app/scripts/metamask-controller.js | 2 +- package.json | 2 +- yarn.lock | 22 +++++++++++----------- 5 files changed, 17 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dbaab1961..d49318361 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Current Develop Branch +## 7.7.6 Mon Mar 02 2020 +- [#8154](https://github.com/MetaMask/metamask-extension/pull/8154): Prevent signing from incorrect Ledger account + ## 7.7.5 Fri Feb 14 2020 - [#8053](https://github.com/MetaMask/metamask-extension/pull/8053): Inline the source text not the binary encoding for inpage script - [#8049](https://github.com/MetaMask/metamask-extension/pull/8049): Add warning to watchAsset API when editing a known token diff --git a/app/manifest.json b/app/manifest.json index 070c6fc7e..905389e15 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "short_name": "__MSG_appName__", - "version": "7.7.5", + "version": "7.7.6", "manifest_version": 2, "author": "https://metamask.io", "description": "__MSG_appDescription__", diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 15e1d6aa2..5c50a8502 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -53,7 +53,7 @@ const percentile = require('percentile') const seedPhraseVerifier = require('./lib/seed-phrase-verifier') const log = require('loglevel') const TrezorKeyring = require('eth-trezor-keyring') -const LedgerBridgeKeyring = require('eth-ledger-bridge-keyring') +const LedgerBridgeKeyring = require('@metamask/eth-ledger-bridge-keyring') const EthQuery = require('eth-query') const ethUtil = require('ethereumjs-util') const contractMap = require('eth-contract-metadata') diff --git a/package.json b/package.json index 19ee54737..649e6f486 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "3box": "^1.10.2", "@babel/runtime": "^7.5.5", "@material-ui/core": "1.0.0", + "@metamask/eth-ledger-bridge-keyring": "^0.2.2", "@sentry/browser": "^4.1.1", "@zxing/library": "^0.8.0", "abi-decoder": "^1.2.0", @@ -94,7 +95,6 @@ "eth-json-rpc-infura": "^4.0.1", "eth-json-rpc-middleware": "^4.4.0", "eth-keyring-controller": "^5.3.0", - "eth-ledger-bridge-keyring": "^0.2.0", "eth-method-registry": "^1.2.0", "eth-phishing-detect": "^1.1.4", "eth-query": "^2.1.2", diff --git a/yarn.lock b/yarn.lock index 4e830229d..8fac0a30c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1985,6 +1985,17 @@ scroll "^2.0.3" warning "^3.0.0" +"@metamask/eth-ledger-bridge-keyring@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@metamask/eth-ledger-bridge-keyring/-/eth-ledger-bridge-keyring-0.2.2.tgz#f75dd45edf17c48b02e49a96b3413e6abf14b7ef" + integrity sha512-c1Gfn01qIqNikx8eFz1jq2KU9Vcfpgkp62v8kYgiWebwkyxuzBKlDWMoC3JCwAV5ezl05M6MQi1EAwrcsIbUOA== + dependencies: + eth-sig-util "^1.4.2" + ethereumjs-tx "^1.3.4" + ethereumjs-util "^5.1.5" + events "^2.0.0" + hdkey "0.8.0" + "@mrmlnc/readdir-enhanced@^2.2.1": version "2.2.1" resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" @@ -10195,17 +10206,6 @@ eth-keyring-controller@^5.3.0: loglevel "^1.5.0" obs-store "^4.0.3" -eth-ledger-bridge-keyring@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/eth-ledger-bridge-keyring/-/eth-ledger-bridge-keyring-0.2.0.tgz#715de14da24496c35cdb433022a20bd21b984f7e" - integrity sha512-js8jhe5fIwnOib3ja8Nk8BWpwVYQreh+Hrq/0ry7iO26TH+SdH/gaBIJD2toyICIUYPYU9wpUw9RABsarxbNiQ== - dependencies: - eth-sig-util "^1.4.2" - ethereumjs-tx "^1.3.4" - ethereumjs-util "^5.1.5" - events "^2.0.0" - hdkey "0.8.0" - eth-lib@0.2.7: version "0.2.7" resolved "https://registry.yarnpkg.com/eth-lib/-/eth-lib-0.2.7.tgz#2f93f17b1e23aec3759cd4a3fe20c1286a3fc1ca" From 0b54b990879abc945b383848d8af94c9b4569836 Mon Sep 17 00:00:00 2001 From: Dan Finlay <542863+danfinlay@users.noreply.github.com> Date: Wed, 4 Mar 2020 14:42:33 -0800 Subject: [PATCH 46/70] Version v7.7.7 RC (#8161) * Version v7.7.7 * Update `@metamask/eth-ledger-bridge-keyring` (#8162) * Update `@metamask/eth-ledger-bridge-keyring` The Ledger keyring has been updated to ensure that any stale BIP44 accounts created prior to v7.7.6 of the extension are discarded when the extension starts. Any attempts to sign with these accounts would have failed; they needed to be re-added regardless. * Update changelog * Fix Ledger account index check for account zero (#8163) Update Ledger keyring to fix bug when trying to sign with account 0 Co-authored-by: Mark Stacey --- CHANGELOG.md | 4 ++++ app/manifest.json | 2 +- package.json | 2 +- yarn.lock | 8 ++++---- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d49318361..f65647bae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Current Develop Branch +## 7.7.7 Wed Mar 04 2020 +- [#8162](https://github.com/MetaMask/metamask-extension/pull/8162): Remove invalid Ledger accounts +- [#8163](https://github.com/MetaMask/metamask-extension/pull/8163): Fix account index check + ## 7.7.6 Mon Mar 02 2020 - [#8154](https://github.com/MetaMask/metamask-extension/pull/8154): Prevent signing from incorrect Ledger account diff --git a/app/manifest.json b/app/manifest.json index 905389e15..08ca74e1f 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "short_name": "__MSG_appName__", - "version": "7.7.6", + "version": "7.7.7", "manifest_version": 2, "author": "https://metamask.io", "description": "__MSG_appDescription__", diff --git a/package.json b/package.json index 649e6f486..578da2a78 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "3box": "^1.10.2", "@babel/runtime": "^7.5.5", "@material-ui/core": "1.0.0", - "@metamask/eth-ledger-bridge-keyring": "^0.2.2", + "@metamask/eth-ledger-bridge-keyring": "^0.2.6", "@sentry/browser": "^4.1.1", "@zxing/library": "^0.8.0", "abi-decoder": "^1.2.0", diff --git a/yarn.lock b/yarn.lock index 8fac0a30c..25712b7ed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1985,10 +1985,10 @@ scroll "^2.0.3" warning "^3.0.0" -"@metamask/eth-ledger-bridge-keyring@^0.2.2": - version "0.2.2" - resolved "https://registry.yarnpkg.com/@metamask/eth-ledger-bridge-keyring/-/eth-ledger-bridge-keyring-0.2.2.tgz#f75dd45edf17c48b02e49a96b3413e6abf14b7ef" - integrity sha512-c1Gfn01qIqNikx8eFz1jq2KU9Vcfpgkp62v8kYgiWebwkyxuzBKlDWMoC3JCwAV5ezl05M6MQi1EAwrcsIbUOA== +"@metamask/eth-ledger-bridge-keyring@^0.2.6": + version "0.2.6" + resolved "https://registry.yarnpkg.com/@metamask/eth-ledger-bridge-keyring/-/eth-ledger-bridge-keyring-0.2.6.tgz#2721c118a5eeb3685d372d0258f2a3b03dd01315" + integrity sha512-7OtX24lHSCioFZM+x4ZCsndT1wkI7cf8+ua3UntYqHFSRu/SDf6xVNBuD8isvWidNbVdcJKhfJuO3vFCDNsPBw== dependencies: eth-sig-util "^1.4.2" ethereumjs-tx "^1.3.4" From a7bcc17f9b7faadf1f837924a1617579167909d3 Mon Sep 17 00:00:00 2001 From: MetaMask Bot <37885440+metamaskbot@users.noreply.github.com> Date: Fri, 13 Mar 2020 13:06:18 -0700 Subject: [PATCH 47/70] Merge pull request #8186 from MetaMask/Version-v7.7.8 Version v7.7.8 RC --- CHANGELOG.md | 4 +++ app/manifest.json | 2 +- app/scripts/controllers/transactions/index.js | 16 ++++++++- app/scripts/metamask-controller.js | 4 +-- .../gas-modal-page-container.container.js | 18 +++++----- .../send-amount-row.component.js | 26 +++++++++++--- .../send-amount-row.container.js | 2 ++ .../tests/send-amount-row-container.test.js | 34 +------------------ ui/app/store/actions.js | 8 ++--- 9 files changed, 60 insertions(+), 54 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f65647bae..2c062db40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Current Develop Branch +## 7.7.8 Wed Mar 11 2020 +- [#8176](https://github.com/MetaMask/metamask-extension/pull/8176): Handle and set gas estimation when max mode is clicked +- [#8178](https://github.com/MetaMask/metamask-extension/pull/8178): Use specified gas limit when speeding up a transaction + ## 7.7.7 Wed Mar 04 2020 - [#8162](https://github.com/MetaMask/metamask-extension/pull/8162): Remove invalid Ledger accounts - [#8163](https://github.com/MetaMask/metamask-extension/pull/8163): Fix account index check diff --git a/app/manifest.json b/app/manifest.json index 08ca74e1f..de4191ab4 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "short_name": "__MSG_appName__", - "version": "7.7.7", + "version": "7.7.8", "manifest_version": 2, "author": "https://metamask.io", "description": "__MSG_appDescription__", diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index 8e4e9c0a7..fa82b4f19 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -312,7 +312,17 @@ class TransactionController extends EventEmitter { return newTxMeta } - async createSpeedUpTransaction (originalTxId, customGasPrice) { + /** + * Creates a new approved transaction to attempt to speed up a previously submitted transaction. The + * new transaction contains the same nonce as the previous. By default, the new transaction will use + * the same gas limit and a 10% higher gas price, though it is possible to set a custom value for + * each instead. + * @param {number} originalTxId - the id of the txMeta that you want to speed up + * @param {string} [customGasPrice] - The new custom gas price, in hex + * @param {string} [customGasLimit] - The new custom gas limt, in hex + * @returns {txMeta} + */ + async createSpeedUpTransaction (originalTxId, customGasPrice, customGasLimit) { const originalTxMeta = this.txStateManager.getTx(originalTxId) const { txParams } = originalTxMeta const { gasPrice: lastGasPrice } = txParams @@ -330,6 +340,10 @@ class TransactionController extends EventEmitter { type: TRANSACTION_TYPE_RETRY, }) + if (customGasLimit) { + newTxMeta.txParams.gas = customGasLimit + } + this.addTx(newTxMeta) await this.approveTransaction(newTxMeta.id) return newTxMeta diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 5c50a8502..bd31705cb 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -1286,8 +1286,8 @@ module.exports = class MetamaskController extends EventEmitter { } } - async createSpeedUpTransaction (originalTxId, customGasPrice) { - await this.txController.createSpeedUpTransaction(originalTxId, customGasPrice) + async createSpeedUpTransaction (originalTxId, customGasPrice, customGasLimit) { + await this.txController.createSpeedUpTransaction(originalTxId, customGasPrice, customGasLimit) const state = await this.getState() return state } 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 c3d214b63..6cb97fef8 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 @@ -108,9 +108,11 @@ const mapStateToProps = (state, ownProps) => { const isMainnet = getIsMainnet(state) const showFiat = Boolean(isMainnet || showFiatInTestnets) - const newTotalEth = maxModeOn ? addHexWEIsToRenderableEth(balance, '0x0') : addHexWEIsToRenderableEth(value, customGasTotal) + const isTokenSelected = Boolean(getSelectedToken(state)) - const sendAmount = maxModeOn ? subtractHexWEIsFromRenderableEth(balance, customGasTotal) : addHexWEIsToRenderableEth(value, '0x0') + const newTotalEth = maxModeOn && !isTokenSelected ? addHexWEIsToRenderableEth(balance, '0x0') : addHexWEIsToRenderableEth(value, customGasTotal) + + const sendAmount = maxModeOn && !isTokenSelected ? subtractHexWEIsFromRenderableEth(balance, customGasTotal) : addHexWEIsToRenderableEth(value, '0x0') const insufficientBalance = maxModeOn ? false : !isBalanceSufficient({ amount: value, @@ -187,11 +189,11 @@ const mapDispatchToProps = dispatch => { dispatch(setCustomGasLimit(addHexPrefix(gasLimit.toString(16)))) return dispatch(updateTransaction(updatedTx)) }, - createSpeedUpTransaction: (txId, gasPrice) => { - return dispatch(createSpeedUpTransaction(txId, gasPrice)) + createRetryTransaction: (txId, gasPrice, gasLimit) => { + return dispatch(createRetryTransaction(txId, gasPrice, gasLimit)) }, - createRetryTransaction: (txId, gasPrice) => { - return dispatch(createRetryTransaction(txId, gasPrice)) + createSpeedUpTransaction: (txId, gasPrice, gasLimit) => { + return dispatch(createSpeedUpTransaction(txId, gasPrice, gasLimit)) }, hideGasButtonGroup: () => dispatch(hideGasButtonGroup()), setCustomTimeEstimate: (timeEstimateInSeconds) => dispatch(setCustomTimeEstimate(timeEstimateInSeconds)), @@ -253,11 +255,11 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { dispatchUpdateConfirmTxGasAndCalculate(gasLimit, gasPrice, updatedTx) dispatchHideModal() } else if (isSpeedUp) { - dispatchCreateSpeedUpTransaction(txId, gasPrice) + dispatchCreateSpeedUpTransaction(txId, gasPrice, gasLimit) dispatchHideSidebar() dispatchCancelAndClose() } else if (isRetry) { - dispatchCreateRetryTransaction(txId, gasPrice) + dispatchCreateRetryTransaction(txId, gasPrice, gasLimit) dispatchHideSidebar() dispatchCancelAndClose() } else { diff --git a/ui/app/pages/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 0b46d48c5..37821fa64 100644 --- a/ui/app/pages/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 @@ -27,12 +27,26 @@ export default class SendAmountRow extends Component { updateSendAmount: PropTypes.func, updateSendAmountError: PropTypes.func, updateGas: PropTypes.func, + maxModeOn: PropTypes.bool, } static contextTypes = { t: PropTypes.func, } + componentDidUpdate (prevProps) { + const { maxModeOn: prevMaxModeOn, gasTotal: prevGasTotal } = prevProps + const { maxModeOn, amount, gasTotal, selectedToken } = this.props + + if (maxModeOn && selectedToken && !prevMaxModeOn) { + this.updateGas(amount) + } + + if (prevGasTotal !== gasTotal) { + this.validateAmount(amount) + } + } + updateGas = debounce(this.updateGas.bind(this), 500) validateAmount (amount) { @@ -87,17 +101,19 @@ export default class SendAmountRow extends Component { } } + handleChange = (newAmount) => { + this.validateAmount(newAmount) + this.updateGas(newAmount) + this.updateAmount(newAmount) + } + renderInput () { const { amount, inError, selectedToken } = this.props const Component = selectedToken ? UserPreferencedTokenInput : UserPreferencedCurrencyInput return ( { - this.validateAmount(newAmount) - this.updateGas(newAmount) - this.updateAmount(newAmount) - }} + onChange={this.handleChange} error={inError} value={amount} /> diff --git a/ui/app/pages/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 2b3470da4..938e797d4 100644 --- a/ui/app/pages/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 @@ -9,6 +9,7 @@ import { getSendAmount, getSendFromBalance, getTokenBalance, + getSendMaxModeState, } from '../../send.selectors' import { sendAmountIsInError, @@ -37,6 +38,7 @@ function mapStateToProps (state) { primaryCurrency: getPrimaryCurrency(state), selectedToken: getSelectedToken(state), tokenBalance: getTokenBalance(state), + maxModeOn: getSendMaxModeState(state), } } diff --git a/ui/app/pages/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 dada1c5e9..bceadb914 100644 --- a/ui/app/pages/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 @@ -2,7 +2,6 @@ import assert from 'assert' import proxyquire from 'proxyquire' import sinon from 'sinon' -let mapStateToProps let mapDispatchToProps const actionSpies = { @@ -15,23 +14,11 @@ const duckActionSpies = { proxyquire('../send-amount-row.container.js', { 'react-redux': { - connect: (ms, md) => { - mapStateToProps = ms + connect: (_, md) => { mapDispatchToProps = md return () => ({}) }, }, - '../../send.selectors': { - getAmountConversionRate: (s) => `mockAmountConversionRate:${s}`, - getConversionRate: (s) => `mockConversionRate:${s}`, - getCurrentCurrency: (s) => `mockConvertedCurrency:${s}`, - getGasTotal: (s) => `mockGasTotal:${s}`, - getPrimaryCurrency: (s) => `mockPrimaryCurrency:${s}`, - getSelectedToken: (s) => `mockSelectedToken:${s}`, - getSendAmount: (s) => `mockAmount:${s}`, - getSendFromBalance: (s) => `mockBalance:${s}`, - getTokenBalance: (s) => `mockTokenBalance:${s}`, - }, './send-amount-row.selectors': { sendAmountIsInError: (s) => `mockInError:${s}` }, '../../send.utils': { getAmountErrorObject: (mockDataObject) => ({ ...mockDataObject, mockChange: true }), @@ -43,25 +30,6 @@ proxyquire('../send-amount-row.container.js', { describe('send-amount-row container', () => { - describe('mapStateToProps()', () => { - - it('should map the correct properties to props', () => { - assert.deepEqual(mapStateToProps('mockState'), { - amount: 'mockAmount:mockState', - amountConversionRate: 'mockAmountConversionRate:mockState', - balance: 'mockBalance:mockState', - conversionRate: 'mockConversionRate:mockState', - convertedCurrency: 'mockConvertedCurrency:mockState', - gasTotal: 'mockGasTotal:mockState', - inError: 'mockInError:mockState', - primaryCurrency: 'mockPrimaryCurrency:mockState', - selectedToken: 'mockSelectedToken:mockState', - tokenBalance: 'mockTokenBalance:mockState', - }) - }) - - }) - describe('mapDispatchToProps()', () => { let dispatchSpy let mapDispatchToPropsObject diff --git a/ui/app/store/actions.js b/ui/app/store/actions.js index 2642e89e9..285216154 100644 --- a/ui/app/store/actions.js +++ b/ui/app/store/actions.js @@ -1843,13 +1843,13 @@ function createCancelTransaction (txId, customGasPrice) { } } -function createSpeedUpTransaction (txId, customGasPrice) { +function createSpeedUpTransaction (txId, customGasPrice, customGasLimit) { log.debug('background.createSpeedUpTransaction') let newTx return dispatch => { return new Promise((resolve, reject) => { - background.createSpeedUpTransaction(txId, customGasPrice, (err, newState) => { + background.createSpeedUpTransaction(txId, customGasPrice, customGasLimit, (err, newState) => { if (err) { dispatch(actions.displayWarning(err.message)) return reject(err) @@ -1865,13 +1865,13 @@ function createSpeedUpTransaction (txId, customGasPrice) { } } -function createRetryTransaction (txId, customGasPrice) { +function createRetryTransaction (txId, customGasPrice, customGasLimit) { log.debug('background.createRetryTransaction') let newTx return dispatch => { return new Promise((resolve, reject) => { - background.createSpeedUpTransaction(txId, customGasPrice, (err, newState) => { + background.createSpeedUpTransaction(txId, customGasPrice, customGasLimit, (err, newState) => { if (err) { dispatch(actions.displayWarning(err.message)) return reject(err) From a7911ce9b3dc05bcb6533259240033cb7de38f9a Mon Sep 17 00:00:00 2001 From: MetaMask Bot Date: Tue, 28 Apr 2020 15:40:28 +0000 Subject: [PATCH 48/70] Version v7.7.9 --- CHANGELOG.md | 2 ++ app/manifest.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c062db40..c01b6cc48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Develop Branch +## 7.7.9 Tue Apr 28 2020 + ## 7.7.8 Wed Mar 11 2020 - [#8176](https://github.com/MetaMask/metamask-extension/pull/8176): Handle and set gas estimation when max mode is clicked - [#8178](https://github.com/MetaMask/metamask-extension/pull/8178): Use specified gas limit when speeding up a transaction diff --git a/app/manifest.json b/app/manifest.json index de4191ab4..45ff5ab7f 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "short_name": "__MSG_appName__", - "version": "7.7.8", + "version": "7.7.9", "manifest_version": 2, "author": "https://metamask.io", "description": "__MSG_appDescription__", From 2a513990c2b31e3e38fd3e741d58a86aea38d6ee Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Wed, 29 Apr 2020 01:57:25 -0300 Subject: [PATCH 49/70] Backport "Fix popup not opening (#8314)" (#8446) This is a backport of #8314. Here's the original description: MetaMask would sometimes get into a state where the notification popup would never open. This could happen if the notification window was closed shortly after being opened. After this happened, no popups would show up until after the extension was reset. This was happening because the background thought the popup was already open. The variable it uses to track whether the popup was open or not was being set to `true` immediately after the background asked the browser to open a new window, before a handler was attached that could respond to the window being closed. Removing this line seems to solve the problem. This line was added originally in #5437, which dealt with batch transactions. Batches of transactions seem to work just fine without this line though (from local testing), and I can't think of why this would be required. Closes #7051 --- CHANGELOG.md | 1 + app/scripts/background.js | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c01b6cc48..9b38bd54f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Current Develop Branch ## 7.7.9 Tue Apr 28 2020 +- [#8446](https://github.com/MetaMask/metamask-extension/pull/8446): Fix popup not opening ## 7.7.8 Wed Mar 11 2020 - [#8176](https://github.com/MetaMask/metamask-extension/pull/8176): Handle and set gas estimation when max mode is clicked diff --git a/app/scripts/background.js b/app/scripts/background.js index 31c8d1815..fc14f68a7 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -447,7 +447,6 @@ function triggerUi () { const currentlyActiveMetamaskTab = Boolean(tabs.find(tab => openMetamaskTabsIDs[tab.id])) if (!popupIsOpen && !currentlyActiveMetamaskTab && !notificationIsOpen) { notificationManager.showPopup() - notificationIsOpen = true } }) } From 8fb615c9f6746a5ea919e2e1609b6fb8f8a88696 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Wed, 29 Apr 2020 02:31:45 -0300 Subject: [PATCH 50/70] Backport "Skip adding history entry for empty txMeta diffs (#8379)" (#8449) Backport #8379 to v7.7.9 --- CHANGELOG.md | 1 + .../transactions/tx-state-manager.js | 4 +++- .../transactions/tx-state-manager-test.js | 17 +++++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b38bd54f..e06195956 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ## 7.7.9 Tue Apr 28 2020 - [#8446](https://github.com/MetaMask/metamask-extension/pull/8446): Fix popup not opening +- [#8449](https://github.com/MetaMask/metamask-extension/pull/8449): Skip adding history entry for empty txMeta diffs ## 7.7.8 Wed Mar 11 2020 - [#8176](https://github.com/MetaMask/metamask-extension/pull/8176): Handle and set gas estimation when max mode is clicked diff --git a/app/scripts/controllers/transactions/tx-state-manager.js b/app/scripts/controllers/transactions/tx-state-manager.js index cf254352f..e2bbe8e39 100644 --- a/app/scripts/controllers/transactions/tx-state-manager.js +++ b/app/scripts/controllers/transactions/tx-state-manager.js @@ -195,7 +195,9 @@ class TransactionStateManager extends EventEmitter { const previousState = txStateHistoryHelper.replayHistory(txMeta.history) // generate history entry and add to history const entry = txStateHistoryHelper.generateHistoryEntry(previousState, currentState, note) - txMeta.history.push(entry) + if (entry.length) { + txMeta.history.push(entry) + } // commit txMeta to state const txId = txMeta.id 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 02d6199e9..bdd1c9cb7 100644 --- a/test/unit/app/controllers/transactions/tx-state-manager-test.js +++ b/test/unit/app/controllers/transactions/tx-state-manager-test.js @@ -252,6 +252,23 @@ describe('TransactionStateManager', function () { assert.deepEqual(result.history[1][0].value, expectedEntry.value, 'two history items (initial + diff) value') assert.ok(result.history[1][0].timestamp >= before && result.history[1][0].timestamp <= after) }) + + it('does NOT add empty history items', function () { + const txMeta = { + id: '1', + status: 'unapproved', + metamaskNetworkId: currentNetworkId, + txParams: { + gasPrice: '0x01', + }, + } + + txStateManager.addTx(txMeta) + txStateManager.updateTx(txMeta) + + const { history } = txStateManager.getTx('1') + assert.equal(history.length, 1, 'two history items (initial + diff)') + }) }) describe('#getUnapprovedTxList', function () { From 57fdc03dc247bae1106f96cddacd11d819e9d591 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Wed, 29 Apr 2020 11:40:08 -0300 Subject: [PATCH 51/70] Backport "Delete Dai/Sai migration notification (#8418)" (#8447) This backports the deletion of the Dai/Sai migration notification (#8418). Note that the migration to delete the now unused background state has not been included, as it is non-essential and would have been more difficult to backport. The migration to delete the unused state will be included in the next major release instead. --- CHANGELOG.md | 1 + app/_locales/en/messages.json | 9 --- app/scripts/controllers/app-state.js | 7 -- app/scripts/metamask-controller.js | 1 - .../dai-migration-notification.component.js | 78 ------------------- .../dai-migration-notification.container.js | 34 -------- .../app/dai-migration-component/index.js | 1 - ui/app/pages/home/home.component.js | 9 --- ui/app/pages/home/home.container.js | 3 +- ui/app/selectors/selectors.js | 7 -- ui/app/store/actions.js | 11 --- 11 files changed, 2 insertions(+), 159 deletions(-) delete mode 100644 ui/app/components/app/dai-migration-component/dai-migration-notification.component.js delete mode 100644 ui/app/components/app/dai-migration-component/dai-migration-notification.container.js delete mode 100644 ui/app/components/app/dai-migration-component/index.js diff --git a/CHANGELOG.md b/CHANGELOG.md index e06195956..4b6226b85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ## 7.7.9 Tue Apr 28 2020 - [#8446](https://github.com/MetaMask/metamask-extension/pull/8446): Fix popup not opening - [#8449](https://github.com/MetaMask/metamask-extension/pull/8449): Skip adding history entry for empty txMeta diffs +- [#8447](https://github.com/MetaMask/metamask-extension/pull/8447): Delete Dai/Sai migration notification ## 7.7.8 Wed Mar 11 2020 - [#8176](https://github.com/MetaMask/metamask-extension/pull/8176): Handle and set gas estimation when max mode is clicked diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 73c59555e..052229bfe 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -1,13 +1,4 @@ { - "migrateSai": { - "message": "A message from Maker: The new Multi-Collateral Dai token has been released. Your old tokens are now called Sai. Please upgrade your Sai tokens to the new Dai." - }, - "migrateSaiInfo": { - "message": "To dismiss this notification you can migrate your tokens or hide SAI from the token list." - }, - "migrate": { - "message": "Migrate" - }, "showIncomingTransactions": { "message": "Show Incoming Transactions" }, diff --git a/app/scripts/controllers/app-state.js b/app/scripts/controllers/app-state.js index c60a1c4f5..32866888a 100644 --- a/app/scripts/controllers/app-state.js +++ b/app/scripts/controllers/app-state.js @@ -13,7 +13,6 @@ class AppStateController { this.onInactiveTimeout = onInactiveTimeout || (() => {}) this.store = new ObservableStore(extend({ timeoutMinutes: 0, - mkrMigrationReminderTimestamp: null, }, initState)) this.timer = null @@ -24,12 +23,6 @@ class AppStateController { this._setInactiveTimeout(preferences.autoLogoutTimeLimit) } - setMkrMigrationReminderTimestamp (timestamp) { - this.store.updateState({ - mkrMigrationReminderTimestamp: timestamp, - }) - } - /** * Sets the last active time to the current time * @return {void} diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index bd31705cb..9eb46a40e 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -509,7 +509,6 @@ module.exports = class MetamaskController extends EventEmitter { // AppStateController setLastActiveTime: nodeify(this.appStateController.setLastActiveTime, this.appStateController), - setMkrMigrationReminderTimestamp: nodeify(this.appStateController.setMkrMigrationReminderTimestamp, this.appStateController), // EnsController tryReverseResolveAddress: nodeify(this.ensController.reverseResolveAddress, this.ensController), diff --git a/ui/app/components/app/dai-migration-component/dai-migration-notification.component.js b/ui/app/components/app/dai-migration-component/dai-migration-notification.component.js deleted file mode 100644 index d26358df7..000000000 --- a/ui/app/components/app/dai-migration-component/dai-migration-notification.component.js +++ /dev/null @@ -1,78 +0,0 @@ -import { DateTime } from 'luxon' -import React, { PureComponent } from 'react' -import PropTypes from 'prop-types' -import HomeNotification from '../home-notification' - -export default class DaiV1MigrationNotification extends PureComponent { - static contextTypes = { - t: PropTypes.func, - } - - static defaultProps = { - mkrMigrationReminderTimestamp: null, - string: '', - symbol: '', - } - - static propTypes = { - setMkrMigrationReminderTimestamp: PropTypes.func.isRequired, - mkrMigrationReminderTimestamp: PropTypes.string, - string: PropTypes.string, - symbol: PropTypes.string, - } - - remindMeLater = () => { - const nextWeek = DateTime.utc().plus({ - days: 7, - }) - this.props.setMkrMigrationReminderTimestamp(nextWeek.toString()) - } - - render () { - const { t } = this.context - const { mkrMigrationReminderTimestamp, string: balanceString, symbol } = this.props - - if (mkrMigrationReminderTimestamp) { - const reminderDateTime = DateTime.fromISO(mkrMigrationReminderTimestamp, { - zone: 'UTC', - }) - if (reminderDateTime > DateTime.utc()) { - return null - } - } - - if (!balanceString || !symbol) { - return null - } - - if (balanceString === '0') { - return null - } - - return ( - - {t('migrateSai')} -   -
    { - window.open('https://blog.makerdao.com/multi-collateral-dai-is-live/', '_blank', 'noopener') - }} - > - {t('learnMore')}. - -
    - )} - acceptText={t('migrate')} - onAccept={() => { - window.open('https://migrate.makerdao.com', '_blank', 'noopener') - }} - ignoreText={t('remindMeLater')} - onIgnore={this.remindMeLater} - infoText={t('migrateSaiInfo')} - /> - ) - } -} diff --git a/ui/app/components/app/dai-migration-component/dai-migration-notification.container.js b/ui/app/components/app/dai-migration-component/dai-migration-notification.container.js deleted file mode 100644 index 175083bce..000000000 --- a/ui/app/components/app/dai-migration-component/dai-migration-notification.container.js +++ /dev/null @@ -1,34 +0,0 @@ -import { connect } from 'react-redux' -import { compose } from 'recompose' -import DaiMigrationNotification from './dai-migration-notification.component' -import withTokenTracker from '../../../helpers/higher-order-components/with-token-tracker' -import { getSelectedAddress, getDaiV1Token } from '../../../selectors/selectors' -import { setMkrMigrationReminderTimestamp } from '../../../store/actions' - -const mapStateToProps = (state) => { - const { - metamask: { - mkrMigrationReminderTimestamp, - }, - } = state - - const userAddress = getSelectedAddress(state) - const oldDai = getDaiV1Token(state) - - return { - mkrMigrationReminderTimestamp, - userAddress, - token: oldDai, - } -} - -const mapDispatchToProps = (dispatch) => { - return { - setMkrMigrationReminderTimestamp: (t) => dispatch(setMkrMigrationReminderTimestamp(t)), - } -} - -export default compose( - connect(mapStateToProps, mapDispatchToProps), - withTokenTracker, -)(DaiMigrationNotification) diff --git a/ui/app/components/app/dai-migration-component/index.js b/ui/app/components/app/dai-migration-component/index.js deleted file mode 100644 index e3c7cec2b..000000000 --- a/ui/app/components/app/dai-migration-component/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './dai-migration-notification.container' diff --git a/ui/app/pages/home/home.component.js b/ui/app/pages/home/home.component.js index e51c82177..f08a0bb47 100644 --- a/ui/app/pages/home/home.component.js +++ b/ui/app/pages/home/home.component.js @@ -4,7 +4,6 @@ import Media from 'react-media' import { Redirect } from 'react-router-dom' import { formatDate } from '../../helpers/utils/util' import HomeNotification from '../../components/app/home-notification' -import DaiMigrationNotification from '../../components/app/dai-migration-component' import MultipleNotifications from '../../components/app/multiple-notifications' import WalletView from '../../components/app/wallet-view' import TransactionView from '../../components/app/transaction-view' @@ -24,7 +23,6 @@ export default class Home extends PureComponent { static defaultProps = { unsetMigratedPrivacyMode: null, - hasDaiV1Token: false, } static propTypes = { @@ -45,7 +43,6 @@ export default class Home extends PureComponent { restoreFromThreeBox: PropTypes.func, setShowRestorePromptToFalse: PropTypes.func, threeBoxLastUpdated: PropTypes.number, - hasDaiV1Token: PropTypes.bool, } componentWillMount () { @@ -89,7 +86,6 @@ export default class Home extends PureComponent { forgottenPassword, providerRequests, history, - hasDaiV1Token, showPrivacyModeNotification, unsetMigratedPrivacyMode, shouldShowSeedPhraseReminder, @@ -176,11 +172,6 @@ export default class Home extends PureComponent { /> : null } - { - hasDaiV1Token - ? - : null - } ) diff --git a/ui/app/pages/home/home.container.js b/ui/app/pages/home/home.container.js index 4a2106a55..1bac780af 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' -import { getCurrentEthBalance, getDaiV1Token } from '../../selectors/selectors' +import { getCurrentEthBalance } from '../../selectors/selectors' import { unsetMigratedPrivacyMode, restoreFromThreeBox, @@ -44,7 +44,6 @@ const mapStateToProps = state => { showRestorePrompt, selectedAddress, threeBoxLastUpdated, - hasDaiV1Token: Boolean(getDaiV1Token(state)), } } diff --git a/ui/app/selectors/selectors.js b/ui/app/selectors/selectors.js index d606b6db8..fab5f1dae 100644 --- a/ui/app/selectors/selectors.js +++ b/ui/app/selectors/selectors.js @@ -49,7 +49,6 @@ const selectors = { getAccountType, getNumberOfAccounts, getNumberOfTokens, - getDaiV1Token, isEthereumNetwork, getMetaMetricState, getRpcPrefsForCurrentProvider, @@ -226,12 +225,6 @@ function getAddressBookEntryName (state, address) { return entry && entry.name !== '' ? entry.name : addressSlicer(address) } -function getDaiV1Token (state) { - const OLD_DAI_CONTRACT_ADDRESS = '0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359' - const tokens = state.metamask.tokens || [] - return tokens.find(({address}) => checksumAddress(address) === OLD_DAI_CONTRACT_ADDRESS) -} - function accountsWithSendEtherInfoSelector (state) { const accounts = getMetaMaskAccounts(state) const { identities } = state.metamask diff --git a/ui/app/store/actions.js b/ui/app/store/actions.js index 285216154..19c9bfaea 100644 --- a/ui/app/store/actions.js +++ b/ui/app/store/actions.js @@ -367,7 +367,6 @@ var actions = { // AppStateController-related actions SET_LAST_ACTIVE_TIME: 'SET_LAST_ACTIVE_TIME', setLastActiveTime, - setMkrMigrationReminderTimestamp, getContractMethodData, loadingMethoDataStarted, @@ -2756,16 +2755,6 @@ function setLastActiveTime () { } } -function setMkrMigrationReminderTimestamp (timestamp) { - return (dispatch) => { - background.setMkrMigrationReminderTimestamp(timestamp, (err) => { - if (err) { - return dispatch(actions.displayWarning(err.message)) - } - }) - } -} - function loadingMethoDataStarted () { return { type: actions.LOADING_METHOD_DATA_STARTED, From 08fd6cf329ea4665921f91c22183f42f4ccb8c6a Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Wed, 29 Apr 2020 15:17:29 -0300 Subject: [PATCH 52/70] Backport "Update deposit copy for Wyre (#7654)" (#8460) Backport #7654 to v7.7.9 Co-authored-by: Tyson Malchow --- CHANGELOG.md | 1 + app/_locales/en/messages.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b6226b85..88de7b535 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - [#8446](https://github.com/MetaMask/metamask-extension/pull/8446): Fix popup not opening - [#8449](https://github.com/MetaMask/metamask-extension/pull/8449): Skip adding history entry for empty txMeta diffs - [#8447](https://github.com/MetaMask/metamask-extension/pull/8447): Delete Dai/Sai migration notification +- [#8460](https://github.com/MetaMask/metamask-extension/pull/8460): Update deposit copy for Wyre ## 7.7.8 Wed Mar 11 2020 - [#8176](https://github.com/MetaMask/metamask-extension/pull/8176): Handle and set gas estimation when max mode is clicked diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 052229bfe..412d78148 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -229,7 +229,7 @@ "message": "Buy ETH with Wyre" }, "buyWithWyreDescription": { - "message": "Wyre lets you use a credit card to deposit ETH right in to your MetaMask account." + "message": "Wyre lets you use a debit card to deposit ETH right in to your MetaMask account." }, "buyCoinSwitch": { "message": "Buy on CoinSwitch" From 50b48b7a03274f7b4f11f2f168f96c8cb7035507 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Wed, 29 Apr 2020 15:37:26 -0300 Subject: [PATCH 53/70] Backport "Clean up list of available currencies (#7667)" (#8454) This is backported to make it easier to backport #7986 Co-authored-by: Whymarrh Whitby --- test/unit/balance-formatter-test.js | 6 +- .../constants/available-conversions.json | 238 +++++++ .../helpers/constants/infura-conversion.json | 653 ------------------ .../settings-tab/settings-tab.component.js | 10 +- 4 files changed, 246 insertions(+), 661 deletions(-) create mode 100644 ui/app/helpers/constants/available-conversions.json delete mode 100644 ui/app/helpers/constants/infura-conversion.json diff --git a/test/unit/balance-formatter-test.js b/test/unit/balance-formatter-test.js index bd0eb5008..52a442a00 100644 --- a/test/unit/balance-formatter-test.js +++ b/test/unit/balance-formatter-test.js @@ -1,13 +1,13 @@ const assert = require('assert') const currencyFormatter = require('currency-formatter') -const infuraConversion = require('../../ui/app/helpers/constants/infura-conversion.json') +const availableCurrencies = require('../../ui/app/helpers/constants/available-conversions.json') describe('currencyFormatting', function () { it('be able to format any infura currency', function (done) { const number = 10000 - infuraConversion.objects.forEach((conversion) => { - const code = conversion.quote.code.toUpperCase() + availableCurrencies.forEach((conversion) => { + const code = conversion.code.toUpperCase() const result = currencyFormatter.format(number, { code }) switch (code) { diff --git a/ui/app/helpers/constants/available-conversions.json b/ui/app/helpers/constants/available-conversions.json new file mode 100644 index 000000000..61722de39 --- /dev/null +++ b/ui/app/helpers/constants/available-conversions.json @@ -0,0 +1,238 @@ +[ + { + "code": "aud", + "name": "Australian Dollar" + }, + { + "code": "hkd", + "name": "Hong Kong Dollar" + }, + { + "code": "sgd", + "name": "Singapore Dollar" + }, + { + "code": "idr", + "name": "Indonesian Rupiah" + }, + { + "code": "php", + "name": "Philippine Peso" + }, + { + "code": "1st", + "name": "FirstBlood" + }, + { + "code": "adt", + "name": "adToken" + }, + { + "code": "adx", + "name": "AdEx" + }, + { + "code": "ant", + "name": "Aragon" + }, + { + "code": "bat", + "name": "Basic Attention Token" + }, + { + "code": "bnt", + "name": "Bancor" + }, + { + "code": "btc", + "name": "Bitcoin" + }, + { + "code": "cad", + "name": "Canadian Dollar" + }, + { + "code": "cfi", + "name": "Cofound.it" + }, + { + "code": "crb", + "name": "CreditBit" + }, + { + "code": "cvc", + "name": "Civic" + }, + { + "code": "dash", + "name": "Dash" + }, + { + "code": "dgd", + "name": "DigixDAO" + }, + { + "code": "etc", + "name": "Ethereum Classic" + }, + { + "code": "eur", + "name": "Euro" + }, + { + "code": "fun", + "name": "FunFair" + }, + { + "code": "gbp", + "name": "Pound Sterling" + }, + { + "code": "gno", + "name": "Gnosis" + }, + { + "code": "gnt", + "name": "Golem" + }, + { + "code": "gup", + "name": "Matchpool" + }, + { + "code": "hmq", + "name": "Humaniq" + }, + { + "code": "jpy", + "name": "Japanese Yen" + }, + { + "code": "lgd", + "name": "Legends Room" + }, + { + "code": "lsk", + "name": "Lisk" + }, + { + "code": "ltc", + "name": "Litecoin" + }, + { + "code": "lun", + "name": "Lunyr" + }, + { + "code": "mco", + "name": "Monaco" + }, + { + "code": "mtl", + "name": "Metal" + }, + { + "code": "myst", + "name": "Mysterium" + }, + { + "code": "nmr", + "name": "Numeraire" + }, + { + "code": "omg", + "name": "OmiseGO" + }, + { + "code": "pay", + "name": "TenX" + }, + { + "code": "ptoy", + "name": "Patientory" + }, + { + "code": "qrl", + "name": "Quantum-Resistant Ledger" + }, + { + "code": "qtum", + "name": "Qtum" + }, + { + "code": "rep", + "name": "Augur" + }, + { + "code": "rlc", + "name": "iEx.ec" + }, + { + "code": "rub", + "name": "Russian Ruble" + }, + { + "code": "sc", + "name": "Siacoin" + }, + { + "code": "sngls", + "name": "SingularDTV" + }, + { + "code": "snt", + "name": "Status" + }, + { + "code": "steem", + "name": "Steem" + }, + { + "code": "storj", + "name": "Storj" + }, + { + "code": "time", + "name": "ChronoBank" + }, + { + "code": "tkn", + "name": "TokenCard" + }, + { + "code": "trst", + "name": "WeTrust" + }, + { + "code": "uah", + "name": "Ukrainian Hryvnia" + }, + { + "code": "usd", + "name": "United States Dollar" + }, + { + "code": "wings", + "name": "Wings" + }, + { + "code": "xem", + "name": "NEM" + }, + { + "code": "xlm", + "name": "Stellar Lumen" + }, + { + "code": "xmr", + "name": "Monero" + }, + { + "code": "xrp", + "name": "Ripple" + }, + { + "code": "zec", + "name": "Zcash" + } +] diff --git a/ui/app/helpers/constants/infura-conversion.json b/ui/app/helpers/constants/infura-conversion.json deleted file mode 100644 index 9a96fe069..000000000 --- a/ui/app/helpers/constants/infura-conversion.json +++ /dev/null @@ -1,653 +0,0 @@ -{ - "objects": [ - { - "symbol": "ethaud", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "aud", - "name": "Australian Dollar" - } - }, - { - "symbol": "ethhkd", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "hkd", - "name": "Hong Kong Dollar" - } - }, - { - "symbol": "ethsgd", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "sgd", - "name": "Singapore Dollar" - } - }, - { - "symbol": "ethidr", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "idr", - "name": "Indonesian Rupiah" - } - }, - { - "symbol": "ethphp", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "php", - "name": "Philippine Peso" - } - }, - { - "symbol": "eth1st", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "1st", - "name": "FirstBlood" - } - }, - { - "symbol": "ethadt", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "adt", - "name": "adToken" - } - }, - { - "symbol": "ethadx", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "adx", - "name": "AdEx" - } - }, - { - "symbol": "ethant", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "ant", - "name": "Aragon" - } - }, - { - "symbol": "ethbat", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "bat", - "name": "Basic Attention Token" - } - }, - { - "symbol": "ethbnt", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "bnt", - "name": "Bancor" - } - }, - { - "symbol": "ethbtc", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "btc", - "name": "Bitcoin" - } - }, - { - "symbol": "ethcad", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "cad", - "name": "Canadian Dollar" - } - }, - { - "symbol": "ethcfi", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "cfi", - "name": "Cofound.it" - } - }, - { - "symbol": "ethcrb", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "crb", - "name": "CreditBit" - } - }, - { - "symbol": "ethcvc", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "cvc", - "name": "Civic" - } - }, - { - "symbol": "ethdash", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "dash", - "name": "Dash" - } - }, - { - "symbol": "ethdgd", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "dgd", - "name": "DigixDAO" - } - }, - { - "symbol": "ethetc", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "etc", - "name": "Ethereum Classic" - } - }, - { - "symbol": "etheur", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "eur", - "name": "Euro" - } - }, - { - "symbol": "ethfun", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "fun", - "name": "FunFair" - } - }, - { - "symbol": "ethgbp", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "gbp", - "name": "Pound Sterling" - } - }, - { - "symbol": "ethgno", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "gno", - "name": "Gnosis" - } - }, - { - "symbol": "ethgnt", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "gnt", - "name": "Golem" - } - }, - { - "symbol": "ethgup", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "gup", - "name": "Matchpool" - } - }, - { - "symbol": "ethhmq", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "hmq", - "name": "Humaniq" - } - }, - { - "symbol": "ethjpy", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "jpy", - "name": "Japanese Yen" - } - }, - { - "symbol": "ethlgd", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "lgd", - "name": "Legends Room" - } - }, - { - "symbol": "ethlsk", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "lsk", - "name": "Lisk" - } - }, - { - "symbol": "ethltc", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "ltc", - "name": "Litecoin" - } - }, - { - "symbol": "ethlun", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "lun", - "name": "Lunyr" - } - }, - { - "symbol": "ethmco", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "mco", - "name": "Monaco" - } - }, - { - "symbol": "ethmtl", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "mtl", - "name": "Metal" - } - }, - { - "symbol": "ethmyst", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "myst", - "name": "Mysterium" - } - }, - { - "symbol": "ethnmr", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "nmr", - "name": "Numeraire" - } - }, - { - "symbol": "ethomg", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "omg", - "name": "OmiseGO" - } - }, - { - "symbol": "ethpay", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "pay", - "name": "TenX" - } - }, - { - "symbol": "ethptoy", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "ptoy", - "name": "Patientory" - } - }, - { - "symbol": "ethqrl", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "qrl", - "name": "Quantum-Resistant Ledger" - } - }, - { - "symbol": "ethqtum", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "qtum", - "name": "Qtum" - } - }, - { - "symbol": "ethrep", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "rep", - "name": "Augur" - } - }, - { - "symbol": "ethrlc", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "rlc", - "name": "iEx.ec" - } - }, - { - "symbol": "ethrub", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "rub", - "name": "Russian Ruble" - } - }, - { - "symbol": "ethsc", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "sc", - "name": "Siacoin" - } - }, - { - "symbol": "ethsngls", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "sngls", - "name": "SingularDTV" - } - }, - { - "symbol": "ethsnt", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "snt", - "name": "Status" - } - }, - { - "symbol": "ethsteem", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "steem", - "name": "Steem" - } - }, - { - "symbol": "ethstorj", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "storj", - "name": "Storj" - } - }, - { - "symbol": "ethtime", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "time", - "name": "ChronoBank" - } - }, - { - "symbol": "ethtkn", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "tkn", - "name": "TokenCard" - } - }, - { - "symbol": "ethtrst", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "trst", - "name": "WeTrust" - } - }, - { - "symbol": "ethuah", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "uah", - "name": "Ukrainian Hryvnia" - } - }, - { - "symbol": "ethusd", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "usd", - "name": "United States Dollar" - } - }, - { - "symbol": "ethwings", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "wings", - "name": "Wings" - } - }, - { - "symbol": "ethxem", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "xem", - "name": "NEM" - } - }, - { - "symbol": "ethxlm", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "xlm", - "name": "Stellar Lumen" - } - }, - { - "symbol": "ethxmr", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "xmr", - "name": "Monero" - } - }, - { - "symbol": "ethxrp", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "xrp", - "name": "Ripple" - } - }, - { - "symbol": "ethzec", - "base": { - "code": "eth", - "name": "Ethereum" - }, - "quote": { - "code": "zec", - "name": "Zcash" - } - } - ] -} diff --git a/ui/app/pages/settings/settings-tab/settings-tab.component.js b/ui/app/pages/settings/settings-tab/settings-tab.component.js index f8daa98f9..d62838a97 100644 --- a/ui/app/pages/settings/settings-tab/settings-tab.component.js +++ b/ui/app/pages/settings/settings-tab/settings-tab.component.js @@ -1,15 +1,15 @@ import React, { PureComponent } from 'react' import PropTypes from 'prop-types' -import infuraCurrencies from '../../../helpers/constants/infura-conversion.json' +import availableCurrencies from '../../../helpers/constants/available-conversions' import SimpleDropdown from '../../../components/app/dropdowns/simple-dropdown' import ToggleButton from '../../../components/ui/toggle-button' import locales from '../../../../../app/_locales/index.json' -const sortedCurrencies = infuraCurrencies.objects.sort((a, b) => { - return a.quote.name.toLocaleLowerCase().localeCompare(b.quote.name.toLocaleLowerCase()) +const sortedCurrencies = availableCurrencies.sort((a, b) => { + return a.name.toLocaleLowerCase().localeCompare(b.name.toLocaleLowerCase()) }) -const infuraCurrencyOptions = sortedCurrencies.map(({ quote: { code, name } }) => { +const currencyOptions = sortedCurrencies.map(({ code, name }) => { return { displayValue: `${code.toUpperCase()} - ${name}`, key: code, @@ -63,7 +63,7 @@ export default class SettingsTab extends PureComponent {
    setCurrentCurrency(newCurrency)} /> From f91bd3a08dae076f5af3ffd622d267c8ee6bbe2d Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Wed, 29 Apr 2020 16:17:52 -0300 Subject: [PATCH 54/70] Backport "Snapshot txMeta without cloning history (#8363)" (#8458) Backport #8363 to v7.7.9. Note that this uses `clone` instead of `cloneDeep`, because `clone` hadn't yet been replaced by `cloneDeep` on `master`. Backporting that change as well would have been very disruptive, so I've updated this to use `clone` instead to minimize conflicts. It is functionally equivalent. Co-authored-by: Whymarrh Whitby --- CHANGELOG.md | 1 + .../transactions/lib/tx-state-history-helper.js | 15 +++++++-------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88de7b535..ad4f1102c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - [#8449](https://github.com/MetaMask/metamask-extension/pull/8449): Skip adding history entry for empty txMeta diffs - [#8447](https://github.com/MetaMask/metamask-extension/pull/8447): Delete Dai/Sai migration notification - [#8460](https://github.com/MetaMask/metamask-extension/pull/8460): Update deposit copy for Wyre +- [#8458](https://github.com/MetaMask/metamask-extension/pull/8458): Snapshot txMeta without cloning history ## 7.7.8 Wed Mar 11 2020 - [#8176](https://github.com/MetaMask/metamask-extension/pull/8176): Handle and set gas estimation when max mode is clicked diff --git a/app/scripts/controllers/transactions/lib/tx-state-history-helper.js b/app/scripts/controllers/transactions/lib/tx-state-history-helper.js index 76fc5c35b..20e64696b 100644 --- a/app/scripts/controllers/transactions/lib/tx-state-history-helper.js +++ b/app/scripts/controllers/transactions/lib/tx-state-history-helper.js @@ -57,13 +57,12 @@ function replayHistory (_shortHistory) { } /** - @param txMeta {Object} - @returns {object} a clone object of the txMeta with out history -*/ + * Snapshot {@code txMeta} + * @param {Object} txMeta - the tx metadata object + * @returns {Object} a deep clone without history + */ function snapshotFromTxMeta (txMeta) { - // create txMeta snapshot for history - const snapshot = clone(txMeta) - // dont include previous history in this snapshot - delete snapshot.history - return snapshot + const shallow = { ...txMeta } + delete shallow.history + return clone(shallow) } From 3dc8387a966e44b5df8d9c3e1fcbfa9fa20fdbd9 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Wed, 29 Apr 2020 17:02:16 -0300 Subject: [PATCH 55/70] Backport "Fix method registry initialization (#8200)" (#8459) Backport #8200 to v7.7.9. Original commit description: The method registry was being initialized with the global variable `ethereumProvider` before that variable was set. As a result, the method registry was falling back to an internally constructed provider that used the wrong provider URL (an obsolete Infura API). This was resulting in an error with the message "Project ID not found". The method registry is now initialized lazily, when it's first needed. This should be well after the initialization of `ethereumProvider`, which occurs during the UI initialization. --- CHANGELOG.md | 1 + ui/app/helpers/utils/transactions.util.js | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad4f1102c..0dd0ce8a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - [#8447](https://github.com/MetaMask/metamask-extension/pull/8447): Delete Dai/Sai migration notification - [#8460](https://github.com/MetaMask/metamask-extension/pull/8460): Update deposit copy for Wyre - [#8458](https://github.com/MetaMask/metamask-extension/pull/8458): Snapshot txMeta without cloning history +- [#8459](https://github.com/MetaMask/metamask-extension/pull/8459): Fix method registry initialization ## 7.7.8 Wed Mar 11 2020 - [#8176](https://github.com/MetaMask/metamask-extension/pull/8176): Handle and set gas estimation when max mode is clicked diff --git a/ui/app/helpers/utils/transactions.util.js b/ui/app/helpers/utils/transactions.util.js index 1f8c6e952..f198635e7 100644 --- a/ui/app/helpers/utils/transactions.util.js +++ b/ui/app/helpers/utils/transactions.util.js @@ -47,8 +47,7 @@ async function getMethodFrom4Byte (fourBytePrefix) { return null } } - -const registry = new MethodRegistry({ provider: global.ethereumProvider }) +let registry /** * Attempts to return the method data from the MethodRegistry library, the message registry library and the token abi, in that order of preference @@ -62,6 +61,10 @@ export async function getMethodDataAsync (fourBytePrefix) { return null }) + if (!registry) { + registry = new MethodRegistry({ provider: global.ethereumProvider }) + } + let sig = await registry.lookup(fourBytePrefix) if (!sig) { From 361c57191dcaf0eb147300f9b840845c655b5c67 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Wed, 29 Apr 2020 17:44:27 -0300 Subject: [PATCH 56/70] Backport "Fixes #5706 - Adds Dai/Sai to currency display (#7986)" (#8455) Backport #7986 onto v7.7.9. The original commit message follows: With the change from infura to cryptocompare https://github.com/MetaMask/gaba/pull/30/files#diff-50c3c47cc5fa12e5213a6cc900476f41L41-R48 we have numerous conversion rates to go through and add if we like to. Co-authored-by: Thomas Huang --- CHANGELOG.md | 1 + ui/app/helpers/constants/available-conversions.json | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dd0ce8a8..6a865ddb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - [#8460](https://github.com/MetaMask/metamask-extension/pull/8460): Update deposit copy for Wyre - [#8458](https://github.com/MetaMask/metamask-extension/pull/8458): Snapshot txMeta without cloning history - [#8459](https://github.com/MetaMask/metamask-extension/pull/8459): Fix method registry initialization +- [#8455](https://github.com/MetaMask/metamask-extension/pull/8455): Add Dai/Sai to currency display ## 7.7.8 Wed Mar 11 2020 - [#8176](https://github.com/MetaMask/metamask-extension/pull/8176): Handle and set gas estimation when max mode is clicked diff --git a/ui/app/helpers/constants/available-conversions.json b/ui/app/helpers/constants/available-conversions.json index 61722de39..4acb5f2ad 100644 --- a/ui/app/helpers/constants/available-conversions.json +++ b/ui/app/helpers/constants/available-conversions.json @@ -234,5 +234,13 @@ { "code": "zec", "name": "Zcash" + }, + { + "code": "dai", + "name": "DAI" + }, + { + "code": "sai", + "name": "SAI" } ] From 4d9d732fe25d51ba098fac8c1f37361586d42780 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Wed, 29 Apr 2020 18:34:31 -0300 Subject: [PATCH 57/70] Backport "fix destructuring of lastSelectedProvider (#8197)" (#8461) Backport #8197 to v7.7.9. Co-authored-by: Brad Decker --- .../loading-network-screen/loading-network-screen.container.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/app/components/app/loading-network-screen/loading-network-screen.container.js b/ui/app/components/app/loading-network-screen/loading-network-screen.container.js index 87f1397ce..893ee45d2 100644 --- a/ui/app/components/app/loading-network-screen/loading-network-screen.container.js +++ b/ui/app/components/app/loading-network-screen/loading-network-screen.container.js @@ -7,10 +7,10 @@ const mapStateToProps = state => { const { loadingMessage, currentView, + lastSelectedProvider, } = state.appState const { provider, - lastSelectedProvider, network, } = state.metamask const { rpcTarget, chainId, ticker, nickname, type } = provider From 02329541e543cc5adfd60081a8f5af82876c03da Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Wed, 29 Apr 2020 18:35:08 -0300 Subject: [PATCH 58/70] Backport "Add INR currency option (#7673)" (#8457) Backport #7673 to v7.7.9. Co-authored-by: Whymarrh Whitby --- ui/app/helpers/constants/available-conversions.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ui/app/helpers/constants/available-conversions.json b/ui/app/helpers/constants/available-conversions.json index 4acb5f2ad..913e8795f 100644 --- a/ui/app/helpers/constants/available-conversions.json +++ b/ui/app/helpers/constants/available-conversions.json @@ -15,6 +15,10 @@ "code": "idr", "name": "Indonesian Rupiah" }, + { + "code": "inr", + "name": "Indian Rupee" + }, { "code": "php", "name": "Philippine Peso" From 8a8c774e6d17f55325de1e7007757e672fe5e487 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Wed, 29 Apr 2020 18:35:59 -0300 Subject: [PATCH 59/70] Backport "Fix Kovan and Rinkeby chain IDs (#7762)" (#8462) Backport #7762 to v7.7.9 Co-authored-by: Sirius Tsou --- .../networks-tab/networks-tab.constants.js | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/ui/app/pages/settings/networks-tab/networks-tab.constants.js b/ui/app/pages/settings/networks-tab/networks-tab.constants.js index 1a49ca04f..453a616b2 100644 --- a/ui/app/pages/settings/networks-tab/networks-tab.constants.js +++ b/ui/app/pages/settings/networks-tab/networks-tab.constants.js @@ -17,21 +17,12 @@ const defaultNetworksData = [ 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', + chainId: '4', ticker: 'ETH', blockExplorerUrl: 'https://rinkeby.etherscan.io', }, @@ -44,6 +35,15 @@ const defaultNetworksData = [ ticker: 'ETH', blockExplorerUrl: 'https://goerli.etherscan.io', }, + { + labelKey: 'kovan', + iconColor: '#9064FF', + providerType: 'kovan', + rpcUrl: 'https://api.infura.io/v1/jsonrpc/kovan', + chainId: '42', + ticker: 'ETH', + blockExplorerUrl: 'https://etherscan.io', + }, { labelKey: 'localhost', iconColor: 'white', From aab262a3d120f41554dee13118795e1b0a8d5cf1 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Wed, 29 Apr 2020 18:40:20 -0300 Subject: [PATCH 60/70] Backport "Use ethereum-ens-network-map for network support (#7960)" (#8465) Backport #7960 to v7.7.9 Co-authored-by: Whymarrh Whitby --- app/scripts/controllers/ens/ens.js | 2 +- package.json | 1 + .../send/send-content/add-recipient/ens-input.component.js | 2 +- yarn.lock | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/scripts/controllers/ens/ens.js b/app/scripts/controllers/ens/ens.js index eb2586a7d..71996f4de 100644 --- a/app/scripts/controllers/ens/ens.js +++ b/app/scripts/controllers/ens/ens.js @@ -1,5 +1,5 @@ const EthJsEns = require('ethjs-ens') -const ensNetworkMap = require('ethjs-ens/lib/network-map.json') +const ensNetworkMap = require('ethereum-ens-network-map') class Ens { static getNetworkEnsSupport (network) { diff --git a/package.json b/package.json index 578da2a78..8628c6bbc 100644 --- a/package.json +++ b/package.json @@ -101,6 +101,7 @@ "eth-sig-util": "^2.3.0", "eth-token-tracker": "^1.1.10", "eth-trezor-keyring": "^0.4.0", + "ethereum-ens-network-map": "^1.0.2", "ethereumjs-abi": "^0.6.4", "ethereumjs-tx": "1.3.7", "ethereumjs-util": "5.1.0", diff --git a/ui/app/pages/send/send-content/add-recipient/ens-input.component.js b/ui/app/pages/send/send-content/add-recipient/ens-input.component.js index 11b9c5cb5..48a7d8152 100644 --- a/ui/app/pages/send/send-content/add-recipient/ens-input.component.js +++ b/ui/app/pages/send/send-content/add-recipient/ens-input.component.js @@ -7,7 +7,7 @@ import { ellipsify } from '../../send.utils' import debounce from 'debounce' import copyToClipboard from 'copy-to-clipboard/index' import ENS from 'ethjs-ens' -import networkMap from 'ethjs-ens/lib/network-map.json' +import networkMap from 'ethereum-ens-network-map' import log from 'loglevel' diff --git a/yarn.lock b/yarn.lock index 25712b7ed..12a9a4124 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10369,7 +10369,7 @@ ethereum-common@^0.0.18: resolved "https://registry.yarnpkg.com/ethereum-common/-/ethereum-common-0.0.18.tgz#2fdc3576f232903358976eb39da783213ff9523f" integrity sha1-L9w1dvIykDNYl26znaeDIT/5Uj8= -ethereum-ens-network-map@^1.0.0: +ethereum-ens-network-map@^1.0.0, ethereum-ens-network-map@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/ethereum-ens-network-map/-/ethereum-ens-network-map-1.0.2.tgz#4e27bad18dae7bd95d84edbcac2c9e739fc959b9" integrity sha512-5qwJ5n3YhjSpE6O/WEBXCAb2nagUgyagJ6C0lGUBWC4LjKp/rRzD+pwtDJ6KCiITFEAoX4eIrWOjRy0Sylq5Hg== From 2d36d422ee6a015c9074b9afd70a1cbe0a0d81ad Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Wed, 29 Apr 2020 18:40:34 -0300 Subject: [PATCH 61/70] Backport "Updating deprecated Etherscam link (#7464)" (#8463) Backport #7464 to v7.7.9 Co-authored-by: Alice Henshaw <34962750+alicevhenshaw@users.noreply.github.com> Co-authored-by: Whymarrh Whitby --- app/phishing.html | 2 +- app/scripts/phishing-detect.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/phishing.html b/app/phishing.html index 1e1c4d11c..515beea81 100644 --- a/app/phishing.html +++ b/app/phishing.html @@ -47,7 +47,7 @@ Ethereum Phishing Detector. Domains on these warning lists may include outright malicious websites and legitimate websites that have been compromised by a malicious actor.

    -

    To read more about this site please review the domain on Etherscam.

    +

    To read more about this site please search for the domain on CryptoScamDB.

    Note that this warning list is compiled on a voluntary basis. This list may be inaccurate or incomplete. Just because a domain does not appear on this list is not an implicit guarantee of that domain's safety. diff --git a/app/scripts/phishing-detect.js b/app/scripts/phishing-detect.js index 266e4fc31..ad62749bb 100644 --- a/app/scripts/phishing-detect.js +++ b/app/scripts/phishing-detect.js @@ -14,7 +14,7 @@ function start () { const hash = window.location.hash.substring(1) const suspect = querystring.parse(hash) - document.getElementById('esdbLink').href = `https://etherscamdb.info/domain/${suspect.hostname}` + document.getElementById('csdbLink').href = `https://cryptoscamdb.org/search` global.platform = new ExtensionPlatform() global.METAMASK_UI_TYPE = windowType From 14d4c107e54e2a3000a826818b00667635033611 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Thu, 30 Apr 2020 11:38:33 -0300 Subject: [PATCH 62/70] Backport "Don't updatePendingTxs outside of block updates (#8445)" (#8474) Backport #8445 to v7.7.9. Original commit description: * Don't updatePendingTxs outside of block updates Refs #8377 Reverts 507397f6c (#5431) * Check for new block data on unlock Co-authored-by: Whymarrh Whitby --- app/scripts/controllers/transactions/index.js | 1 - app/scripts/metamask-controller.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index fa82b4f19..808c224c3 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -724,7 +724,6 @@ class TransactionController extends EventEmitter { Updates the memStore in transaction controller */ _updateMemstore () { - this.pendingTxTracker.updatePendingTxs() const unapprovedTxs = this.txStateManager.getUnapprovedTxList() const selectedAddressTxList = this.txStateManager.getFilteredTxList({ from: this.getSelectedAddress(), diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 9eb46a40e..0d7b0a598 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -767,7 +767,7 @@ module.exports = class MetamaskController extends EventEmitter { } await this.preferencesController.syncAddresses(accounts) - await this.txController.pendingTxTracker.updatePendingTxs() + await this.blockTracker.checkForLatestBlock() try { const threeBoxSyncingAllowed = this.threeBoxController.getThreeBoxSyncingState() From b64cbbdbacd7b1af25424feabc881307b90a9643 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Thu, 30 Apr 2020 11:38:55 -0300 Subject: [PATCH 63/70] Backport "update eth-contract-metadata (#8466)" (#8476) Backport #8466 to v7.7.9 Co-authored-by: Erik Marks <25517051+rekmarks@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 8628c6bbc..8fb06842d 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "dnode": "^1.2.2", "end-of-stream": "^1.1.0", "eth-block-tracker": "^4.4.2", - "eth-contract-metadata": "^1.12.1", + "eth-contract-metadata": "^1.13.0", "eth-ens-namehash": "^2.0.8", "eth-json-rpc-errors": "^1.1.0", "eth-json-rpc-filters": "^4.1.1", diff --git a/yarn.lock b/yarn.lock index 12a9a4124..f83ad6d26 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10049,10 +10049,10 @@ eth-block-tracker@^4.4.2: pify "^3.0.0" safe-event-emitter "^1.0.1" -eth-contract-metadata@^1.11.0, eth-contract-metadata@^1.12.1: - version "1.12.1" - resolved "https://registry.yarnpkg.com/eth-contract-metadata/-/eth-contract-metadata-1.12.1.tgz#41014c8c0123453cee15acbcc14299c4d470c759" - integrity sha512-9u2jUcdxaKIv4RvA9RtjyD4+M2yWt4yCulR5bpdQTiG3HUFnN9lHtNL5NIRDpvQVJKerFhexrgEM2WdGP3a6VA== +eth-contract-metadata@^1.11.0, eth-contract-metadata@^1.13.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/eth-contract-metadata/-/eth-contract-metadata-1.13.0.tgz#9819d0e556ea2187da91d6b49ce96abc5fce2a73" + integrity sha512-9CjXHX8IdXysUEvOHdbCsjdAwM1E98jaeK2HeOqm/9S/vOZ8YryaBBt/YSiBq3MkpCwf+d1pEQ53p96rsdy52w== eth-ens-namehash@2.0.8, eth-ens-namehash@^2.0.8: version "2.0.8" From 8d8a17d2011e6e86f901e727af620ea4f14bbd61 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Thu, 30 Apr 2020 11:58:49 -0300 Subject: [PATCH 64/70] Update changelog for v7.7.9 (#8477) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a865ddb6..cc32416cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,13 @@ - [#8458](https://github.com/MetaMask/metamask-extension/pull/8458): Snapshot txMeta without cloning history - [#8459](https://github.com/MetaMask/metamask-extension/pull/8459): Fix method registry initialization - [#8455](https://github.com/MetaMask/metamask-extension/pull/8455): Add Dai/Sai to currency display +- [#8461](https://github.com/MetaMask/metamask-extension/pull/8461): Prevent network switch upon close of network timeout overlay +- [#8457](https://github.com/MetaMask/metamask-extension/pull/8457): Add INR currency option +- [#8462](https://github.com/MetaMask/metamask-extension/pull/8462): Fix display of Kovan and Rinkeby chain IDs +- [#8465](https://github.com/MetaMask/metamask-extension/pull/8465): Use ethereum-ens-network-map for network support +- [#8463](https://github.com/MetaMask/metamask-extension/pull/8463): Update deprecated Etherscam link +- [#8474](https://github.com/MetaMask/metamask-extension/pull/8474): Only update pending transactions upon block update +- [#8476](https://github.com/MetaMask/metamask-extension/pull/8476): Update eth-contract-metadata ## 7.7.8 Wed Mar 11 2020 - [#8176](https://github.com/MetaMask/metamask-extension/pull/8176): Handle and set gas estimation when max mode is clicked From b81558ae8fac602019fec5dee439fbf8e48c7c09 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Mon, 4 May 2020 13:43:22 -0300 Subject: [PATCH 65/70] Backport "fixed Tohen Typo (#7808)" (#8509) Backport #7808 to v7.7.9 Co-authored-by: Lenard Frommelt --- CHANGELOG.md | 1 + app/_locales/de/messages.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc32416cb..b114a57e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ - [#8463](https://github.com/MetaMask/metamask-extension/pull/8463): Update deprecated Etherscam link - [#8474](https://github.com/MetaMask/metamask-extension/pull/8474): Only update pending transactions upon block update - [#8476](https://github.com/MetaMask/metamask-extension/pull/8476): Update eth-contract-metadata +- [#8509](https://github.com/MetaMask/metamask-extension/pull/8509): Fix Tohen Typo ## 7.7.8 Wed Mar 11 2020 - [#8176](https://github.com/MetaMask/metamask-extension/pull/8176): Handle and set gas estimation when max mode is clicked diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json index 5f241ab05..705d7f92e 100644 --- a/app/_locales/de/messages.json +++ b/app/_locales/de/messages.json @@ -328,7 +328,7 @@ "message": "Höhere Gebühren können Bearbeitungszeiten verkürzen, wofür es allerdings keine Garantie gibt." }, "customToken": { - "message": "Custom-Tohen" + "message": "Custom-Token" }, "customRPC": { "message": "Spezieller RPC" From 9a624dd24c58d6074cbb4692c2bf54e10557888b Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Mon, 1 Jun 2020 16:14:37 -0300 Subject: [PATCH 66/70] Revert the revert of "LoginPerSite" This reverts commit 466ece458882f28556af8abf424dc7642a9e15e8, which has the message: "Revert "Merge pull request #7599 from MetaMask/Version-v7.7.0" (#7648)" This effectively re-introduces the changes from the "LoginPerSite" PR. --- .circleci/config.yml | 12 + .eslintrc | 26 +- .gitattributes | 1 + CHANGELOG.md | 1 + README.md | 2 +- app/_locales/am/messages.json | 44 +- app/_locales/ar/messages.json | 44 +- app/_locales/bg/messages.json | 48 +- app/_locales/bn/messages.json | 44 +- app/_locales/ca/messages.json | 50 +- app/_locales/cs/messages.json | 33 -- app/_locales/da/messages.json | 48 +- app/_locales/de/messages.json | 48 +- app/_locales/el/messages.json | 48 +- app/_locales/en/messages.json | 135 +++-- app/_locales/es/messages.json | 44 +- app/_locales/es_419/messages.json | 48 +- app/_locales/et/messages.json | 52 +- app/_locales/fa/messages.json | 44 +- app/_locales/fi/messages.json | 48 +- app/_locales/fil/messages.json | 42 +- app/_locales/fr/messages.json | 48 +- app/_locales/gu/messages.json | 9 - app/_locales/he/messages.json | 44 +- app/_locales/hi/messages.json | 44 +- app/_locales/hn/messages.json | 33 -- app/_locales/hr/messages.json | 48 +- app/_locales/ht/messages.json | 33 -- app/_locales/hu/messages.json | 48 +- app/_locales/id/messages.json | 48 +- app/_locales/it/messages.json | 66 +-- app/_locales/ja/messages.json | 37 +- app/_locales/kn/messages.json | 48 +- app/_locales/ko/messages.json | 48 +- app/_locales/lt/messages.json | 48 +- app/_locales/lv/messages.json | 46 +- app/_locales/ml/messages.json | 9 - app/_locales/mr/messages.json | 9 - app/_locales/ms/messages.json | 48 +- app/_locales/nl/messages.json | 33 -- app/_locales/no/messages.json | 48 +- app/_locales/ph/messages.json | 30 -- app/_locales/pl/messages.json | 48 +- app/_locales/pt/messages.json | 33 -- app/_locales/pt_BR/messages.json | 48 +- app/_locales/pt_PT/messages.json | 9 - app/_locales/ro/messages.json | 48 +- app/_locales/ru/messages.json | 44 +- app/_locales/sk/messages.json | 48 +- app/_locales/sl/messages.json | 48 +- app/_locales/sr/messages.json | 48 +- app/_locales/sv/messages.json | 48 +- app/_locales/sw/messages.json | 48 +- app/_locales/ta/messages.json | 36 -- app/_locales/te/messages.json | 9 - app/_locales/th/messages.json | 38 +- app/_locales/tr/messages.json | 29 - app/_locales/uk/messages.json | 48 +- app/_locales/vi/messages.json | 30 -- app/_locales/zh_CN/messages.json | 46 +- app/_locales/zh_TW/messages.json | 44 +- app/images/broken-line.svg | 3 + app/images/connect-white.svg | 3 + ...proval-check.svg => permissions-check.svg} | 0 app/scripts/background.js | 30 +- app/scripts/contentscript.js | 141 ++--- app/scripts/controllers/detect-tokens.js | 42 +- .../controllers/incoming-transactions.js | 4 +- .../controllers/network/middleware/pending.js | 16 +- app/scripts/controllers/network/network.js | 8 +- app/scripts/controllers/onboarding.js | 46 +- app/scripts/controllers/permissions/index.js | 377 +++++++++++++ .../permissions/loggerMiddleware.js | 169 ++++++ .../permissions/methodMiddleware.js | 90 ++++ .../permissions/permissions-safe-methods.json | 49 ++ .../permissions/restrictedMethods.js | 20 + app/scripts/controllers/preferences.js | 62 ++- app/scripts/controllers/provider-approval.js | 175 ------ app/scripts/controllers/recent-blocks.js | 8 +- app/scripts/controllers/threebox.js | 4 +- app/scripts/controllers/token-rates.js | 20 +- app/scripts/controllers/transactions/index.js | 99 ++-- .../lib/tx-state-history-helper.js | 8 +- .../controllers/transactions/lib/util.js | 12 +- .../transactions/pending-tx-tracker.js | 20 +- .../controllers/transactions/tx-gas-utils.js | 8 +- .../transactions/tx-state-manager.js | 24 +- app/scripts/createStandardProvider.js | 73 --- app/scripts/edge-encryptor.js | 28 +- app/scripts/inpage.js | 116 +--- app/scripts/lib/account-tracker.js | 12 +- app/scripts/lib/auto-reload.js | 21 +- app/scripts/lib/buy-eth-url.js | 10 +- app/scripts/lib/cleanErrorStack.js | 4 +- app/scripts/lib/createDnodeRemoteGetter.js | 4 +- app/scripts/lib/createLoggerMiddleware.js | 4 +- app/scripts/lib/createOriginMiddleware.js | 1 + app/scripts/lib/ens-ipfs/setup.js | 8 +- app/scripts/lib/local-store.js | 19 +- app/scripts/lib/message-manager.js | 22 +- app/scripts/lib/migrator/index.js | 8 +- app/scripts/lib/nodeify.js | 6 +- app/scripts/lib/notification-manager.js | 20 +- app/scripts/lib/pending-balance-calculator.js | 4 +- app/scripts/lib/personal-message-manager.js | 22 +- app/scripts/lib/setupFetchDebugging.js | 4 +- app/scripts/lib/setupMetamaskMeshMetrics.js | 4 +- app/scripts/lib/setupSentry.js | 12 +- app/scripts/lib/stream-utils.js | 4 +- app/scripts/lib/typed-message-manager.js | 28 +- app/scripts/lib/util.js | 29 + app/scripts/metamask-controller.js | 341 ++++++++---- app/scripts/migrations/004.js | 5 +- app/scripts/migrations/015.js | 7 +- app/scripts/migrations/016.js | 4 +- app/scripts/migrations/017.js | 4 +- app/scripts/migrations/019.js | 4 +- app/scripts/migrations/022.js | 4 +- app/scripts/migrations/023.js | 11 +- app/scripts/migrations/024.js | 4 +- app/scripts/migrations/025.js | 8 +- app/scripts/migrations/029.js | 1 - app/scripts/migrations/031.js | 2 +- app/scripts/migrations/040.js | 23 + app/scripts/platforms/extension.js | 54 +- development/announcer.js | 12 +- development/auto-changelog.sh | 2 +- development/mock-3box.js | 4 +- development/mock-dev.js | 62 ++- development/rollback.sh | 14 +- development/selector.js | 81 +-- development/show-deps-install-scripts.js | 20 +- development/sourcemap-validator.js | 12 +- development/static-server.js | 92 ++++ development/verify-locale-strings.js | 4 +- development/version-bump.js | 2 + docs/porting_to_new_environment.md | 13 +- gulpfile.js | 4 +- package.json | 23 +- test/e2e/contract-test/contract.js | 489 ++++++++++------- test/e2e/contract-test/index.html | 104 ++-- test/e2e/ethereum-on.spec.js | 28 +- test/e2e/helpers.js | 2 + test/e2e/metamask-responsive-ui.spec.js | 2 +- test/e2e/metamask-ui.spec.js | 24 +- test/e2e/permissions.spec.js | 201 +++++++ test/e2e/run-all.sh | 8 + test/e2e/run-web3.sh | 2 +- test/e2e/signature-request.spec.js | 29 +- test/e2e/web3.spec.js | 37 +- test/helper.js | 16 +- test/integration/index.js | 4 +- test/lib/mock-encryptor.js | 4 +- test/lib/mock-simple-keychain.js | 8 +- test/lib/react-trigger-change.js | 24 +- test/lib/util.js | 8 +- test/setup.js | 8 +- test/unit/actions/config_test.js | 16 +- .../unit/actions/set_selected_account_test.js | 18 +- test/unit/actions/tx_test.js | 16 +- test/unit/actions/view_info_test.js | 14 +- test/unit/actions/warning_test.js | 12 +- test/unit/app/buy-eth-url.spec.js | 2 +- .../app/controllers/detect-tokens-test.js | 10 +- .../controllers/metamask-controller-test.js | 24 +- .../network/pending-middleware-test.js | 8 +- .../preferences-controller-test.js | 47 +- .../app/controllers/provider-approval-test.js | 330 ------------ .../transactions/pending-tx-test.js | 36 +- .../transactions/tx-controller-test.js | 46 +- .../tx-state-history-helper-test.js | 4 +- .../controllers/transactions/tx-utils-test.js | 24 +- test/unit/app/edge-encryptor-test.js | 4 +- test/unit/app/message-manager-test.js | 16 +- test/unit/app/nodeify-test.js | 16 +- .../unit/app/personal-message-manager-test.js | 28 +- test/unit/app/typed-message-manager.spec.js | 12 +- test/unit/migrations/023-test.js | 4 +- test/unit/migrations/024-test.js | 7 +- test/unit/migrations/025-test.js | 8 +- test/unit/migrations/027-test.js | 4 +- test/unit/migrations/029-test.js | 4 +- test/unit/migrations/migrator-test.js | 4 +- test/unit/reducers/unlock_vault_test.js | 10 +- .../responsive/components/dropdown-test.js | 32 +- test/unit/test-utils.js | 4 +- test/unit/ui/app/actions.spec.js | 7 +- test/unit/util_test.js | 124 ++--- test/web3/schema.js | 4 +- test/web3/web3.js | 2 +- .../account-details.component.js | 18 +- .../account-details.container.js | 17 +- .../components/app/account-details/index.scss | 6 + .../account-menu/account-menu.component.js | 35 +- .../account-menu/account-menu.container.js | 9 +- ui/app/components/app/account-menu/index.scss | 4 + ui/app/components/app/account-panel.js | 64 +-- .../app/app-header/app-header.component.js | 3 +- ui/app/components/app/bn-as-decimal-input.js | 188 ------- ...onfirm-page-container-summary.component.js | 8 +- ...confirm-page-container-header.component.js | 54 +- ...irm-page-container-navigation.component.js | 24 +- .../confirm-page-container.component.js | 20 +- .../connected-sites-list.component.js | 157 ++++++ .../connected-sites-list.container.js | 57 ++ .../app/connected-sites-list/index.js | 1 + .../app/connected-sites-list/index.scss | 125 +++++ .../recipient-group.component.js | 8 +- ui/app/components/app/copyable.js | 53 -- .../app/customize-gas-modal/gas-modal-card.js | 48 +- .../app/customize-gas-modal/gas-slider.js | 50 -- .../app/customize-gas-modal/index.js | 132 ++--- .../app/dropdowns/account-details-dropdown.js | 178 +++--- .../app/dropdowns/components/dropdown.js | 69 ++- .../app/dropdowns/components/menu.js | 94 ++-- .../components/network-dropdown-icon.js | 85 +-- .../app/dropdowns/network-dropdown.js | 505 +++++++++--------- .../app/dropdowns/simple-dropdown.js | 98 ++-- .../app/dropdowns/tests/menu.test.js | 14 +- .../tests/network-dropdown-icon.test.js | 14 +- .../dropdowns/tests/network-dropdown.test.js | 2 +- .../app/dropdowns/token-menu-dropdown.js | 51 +- .../advanced-gas-inputs.component.js | 22 +- .../advanced-tab-content.component.js | 22 +- .../advanced-tab-content-component.test.js | 26 +- .../basic-tab-content.component.js | 12 +- .../tests/basic-tab-content-component.test.js | 8 +- .../gas-modal-page-container.component.js | 15 +- ...gas-modal-page-container-component.test.js | 84 +-- .../gas-price-button-group.component.js | 34 +- .../gas-price-button-group-component.test.js | 8 +- .../gas-price-chart/gas-price-chart.utils.js | 4 +- .../gas-slider/gas-slider.component.js | 2 +- ui/app/components/app/index.scss | 12 +- ui/app/components/app/input-number.js | 62 ++- .../loading-network-screen.component.js | 62 ++- ui/app/components/app/menu-droppo.js | 82 +-- .../components/app/modal/modal.component.js | 41 +- .../account-details-modal.component.js | 18 +- .../app/modals/account-modal-container.js | 80 --- .../account-modal-container.component.js | 52 ++ .../account-modal-container.container.js | 20 + .../modals/account-modal-container/index.js | 1 + .../clear-approved-origins.component.js | 39 -- .../modals/clear-approved-origins/index.js | 1 - .../confirm-remove-account.component.js | 4 +- .../app/modals/deposit-ether-modal.js | 213 -------- .../deposit-ether-modal.component.js | 168 ++++++ .../deposit-ether-modal.container.js | 34 ++ .../app/modals/deposit-ether-modal/index.js | 1 + .../disconnect-account.component.js | 52 ++ .../disconnect-account.container.js | 44 ++ .../app/modals/disconnect-account/index.js | 1 + .../app/modals/disconnect-account/index.scss | 25 + .../disconnect-all.component.js | 54 ++ .../disconnect-all.container.js} | 12 +- .../app/modals/disconnect-all/index.js | 1 + .../app/modals/disconnect-all/index.scss | 38 ++ .../edit-approval-permission.component.js | 40 +- .../app/modals/export-private-key-modal.js | 178 ------ .../export-private-key-modal.component.js | 168 ++++++ .../export-private-key-modal.container.js | 38 ++ .../modals/export-private-key-modal/index.js | 1 + .../modals/hide-token-confirmation-modal.js | 76 ++- ui/app/components/app/modals/index.scss | 6 + .../metametrics-opt-in-modal.component.js | 3 +- ui/app/components/app/modals/modal.js | 191 ++++--- .../app/modals/new-account-modal/index.js | 1 + .../app/modals/new-account-modal/index.scss | 37 ++ .../new-account-modal.component.js | 78 +++ .../new-account-modal.container.js | 44 ++ .../app/modals/notification-modal.js | 84 +-- .../multiple-notifications.component.js | 10 +- .../network-display.component.js | 32 +- ui/app/components/app/network.js | 197 ++++--- .../app/permission-page-container/index.js | 3 + .../app/permission-page-container/index.scss | 281 ++++++++++ .../index.js | 1 + ...ission-page-container-content.component.js | 163 ++++++ .../permission-page-container-header/index.js | 1 + ...ission-page-container-header.component.js} | 4 +- .../permission-page-container.component.js | 151 ++++++ .../permission-page-container.container.js | 28 + .../app/provider-page-container/index.js | 3 - .../app/provider-page-container/index.scss | 121 ----- .../provider-page-container-content/index.js | 1 - ...ovider-page-container-content.component.js | 87 --- ...ovider-page-container-content.container.js | 11 - .../provider-page-container-header/index.js | 1 - .../provider-page-container.component.js | 107 ---- .../tests/selected-account-component.test.js | 10 +- ui/app/components/app/shift-list-item.js | 204 ------- .../components/app/shift-list-item/index.js | 1 + .../shift-list-item.component.js | 240 +++++++++ .../shift-list-item.container.js | 12 + .../app/sidebars/sidebar.component.js | 16 +- .../sidebars/tests/sidebars-component.test.js | 14 +- .../app/signature-request-original.js | 357 ------------- .../app/signature-request-original/index.js | 1 + .../signature-request-original.component.js | 324 +++++++++++ .../signature-request-original.container.js | 72 +++ .../signature-request-header.component.js | 10 +- .../tests/signature-request.test.js | 15 +- ui/app/components/app/token-cell.js | 177 ------ ui/app/components/app/token-cell/index.js | 1 + .../app/token-cell/token-cell.component.js | 141 +++++ .../app/token-cell/token-cell.container.js | 25 + ui/app/components/app/token-list.js | 117 ++-- .../transaction-action.component.test.js | 12 +- .../transaction-breakdown.component.js | 26 +- ...transaction-list-item-details.component.js | 21 +- .../transaction-list-item.component.js | 18 +- .../token-view-balance.component.test.js | 38 +- ui/app/components/app/wallet-view.js | 178 ------ ui/app/components/app/wallet-view/index.js | 1 + .../app/wallet-view/wallet-view.component.js | 147 +++++ .../app/wallet-view/wallet-view.container.js | 33 ++ ui/app/components/ui/alert/index.js | 29 +- .../ui/button-group/button-group.stories.js | 8 +- .../tests/button-group-component.test.js | 16 +- ui/app/components/ui/button/button.stories.js | 28 +- ui/app/components/ui/copyButton.js | 120 ++--- .../tests/currency-display.component.test.js | 22 +- ui/app/components/ui/editable-label.js | 96 ++-- ui/app/components/ui/eth-balance.js | 102 ---- .../ui/eth-balance/eth-balance.component.js | 137 +++++ .../ui/eth-balance/eth-balance.container.js | 10 + ui/app/components/ui/eth-balance/index.js | 1 + .../export-text-container.component.js | 49 +- ui/app/components/ui/fiat-value.js | 68 +-- .../tests/hex-to-decimal.component.test.js | 20 +- .../icon-with-fallback.component.js | 42 ++ .../components/ui/icon-with-fallback/index.js | 1 + .../ui/icon-with-fallback/index.scss | 30 ++ .../tests/identicon.component.test.js | 2 +- .../loading-screen.component.js | 34 +- ui/app/components/ui/mascot.js | 23 +- .../page-container-footer.component.js | 22 +- .../page-container-footer.component.test.js | 18 +- .../page-container-header.component.js | 26 +- .../page-container-header.component.test.js | 20 +- ui/app/components/ui/qr-code.js | 95 ++-- ui/app/components/ui/readonly-input.js | 27 +- .../sender-to-recipient.component.js | 7 +- ui/app/components/ui/snackbar/index.js | 1 + ui/app/components/ui/snackbar/index.scss | 11 + .../ui/snackbar/snackbar.component.js | 18 + .../ui/text-field/text-field.stories.js | 28 +- ui/app/components/ui/tooltip.js | 42 +- .../ui/unit-input/unit-input.component.js | 4 +- ui/app/css/itcss/components/confirm.scss | 4 - ui/app/css/itcss/components/index.scss | 1 + ui/app/css/itcss/components/network.scss | 1 - .../css/itcss/components/newui-sections.scss | 4 - ui/app/css/itcss/components/pages/index.scss | 2 +- ...approval.scss => permission-approval.scss} | 4 +- ui/app/css/itcss/components/sections.scss | 1 - ui/app/css/itcss/components/send.scss | 63 --- .../itcss/components/transaction-list.scss | 8 - ui/app/css/itcss/settings/variables.scss | 29 + ui/app/ducks/app/app.js | 22 +- ui/app/ducks/index.js | 4 +- ui/app/ducks/metamask/metamask.js | 4 +- ui/app/helpers/constants/routes.js | 4 + ui/app/helpers/utils/util.js | 125 +++-- ui/app/pages/add-token/add-token.component.js | 6 +- .../token-list/token-list.component.js | 3 +- .../confirm-approve-content.component.js | 57 +- .../confirm-approve.component.js | 42 +- .../confirm-approve.container.js | 4 +- .../confirm-transaction-base.component.js | 68 +-- .../confirm-transaction-base.container.js | 10 +- ui/app/pages/confirm-transaction/conf-tx.js | 59 +- .../confirm-transaction.component.js | 4 +- .../connected-sites.component.js | 36 ++ ui/app/pages/connected-sites/index.js | 1 + ui/app/pages/connected-sites/index.scss | 37 ++ .../connect-hardware/account-list.js | 223 ++++---- .../connect-hardware/connect-screen.js | 406 +++++++------- .../create-account/connect-hardware/index.js | 64 ++- .../create-account.component.js | 18 +- .../create-account/import-account/index.js | 69 ++- .../create-account/import-account/json.js | 109 ++-- .../import-account/private-key.js | 77 +-- .../create-account/import-account/seed.js | 35 -- .../create-account/new-account.component.js | 11 +- .../import-with-seed-phrase.component.js | 3 +- .../new-account/new-account.component.js | 3 +- .../end-of-flow/end-of-flow.component.js | 48 +- .../end-of-flow/end-of-flow.container.js | 7 +- .../first-time-flow/end-of-flow/index.scss | 2 +- .../first-time-flow.selectors.js | 28 +- .../metametrics-opt-in.component.js | 3 +- .../onboarding-initiator-util.js | 48 ++ .../draggable-seed.component.js | 3 +- .../seed-phrase/reveal-seed-phrase/index.scss | 2 +- .../reveal-seed-phrase.component.js | 47 +- .../reveal-seed-phrase.container.js | 9 +- ui/app/pages/home/home.component.js | 102 ++-- ui/app/pages/home/home.container.js | 12 +- ui/app/pages/index.scss | 4 + ui/app/pages/keychains/reveal-seed.js | 171 +++--- ui/app/pages/mobile-sync/index.js | 416 +-------------- .../mobile-sync/mobile-sync.component.js | 436 +++++++++++++++ .../mobile-sync/mobile-sync.container.js | 25 + .../choose-account.component.js | 108 ++++ .../choose-account/index.js | 1 + .../choose-account/index.scss | 97 ++++ ui/app/pages/permissions-connect/index.js | 1 + ui/app/pages/permissions-connect/index.scss | 11 + .../permissions-connect-footer/index.js | 1 + .../permissions-connect-footer/index.scss | 27 + .../permissions-connect-footer.component.js | 27 + .../permissions-connect-header/index.js | 1 + .../permissions-connect-header/index.scss | 15 + .../permissions-connect-header.component.js | 25 + .../permissions-connect.component.js | 208 ++++++++ .../permissions-connect.container.js | 66 +++ ui/app/pages/provider-approval/index.js | 1 - .../provider-approval.component.js | 36 -- .../provider-approval.container.js | 12 - ui/app/pages/routes/index.js | 68 ++- .../account-list-item.component.js | 72 +-- .../tests/account-list-item-component.test.js | 24 +- .../add-recipient/add-recipient.component.js | 3 +- .../add-recipient/ens-input.component.js | 17 +- .../tests/add-recipient-component.test.js | 36 +- .../tests/amount-max-button-component.test.js | 20 +- .../tests/send-amount-row-component.test.js | 36 +- .../send-asset-row.component.js | 3 +- .../send-dropdown-list.component.js | 40 +- .../send-dropdown-list-component.test.js | 24 +- .../gas-fee-display.component.js | 16 +- .../test/gas-fee-display.component.test.js | 18 +- .../send-gas-row/send-gas-row.component.js | 117 ++-- .../tests/send-gas-row-component.test.js | 30 +- .../send-row-error-message-component.test.js | 10 +- .../tests/send-row-wrapper-component.test.js | 52 +- .../tests/send-footer-component.test.js | 90 ++-- .../tests/send-header-component.test.js | 12 +- ui/app/pages/send/send.component.js | 4 +- ui/app/pages/send/send.utils.js | 12 +- .../pages/send/tests/send-component.test.js | 56 +- ui/app/pages/send/tests/send-utils.test.js | 8 +- .../pages/send/to-autocomplete.component.js | 141 ----- ui/app/pages/send/to-autocomplete/index.js | 1 - .../send/to-autocomplete/to-autocomplete.js | 121 ----- .../connected-site-row.component.js | 31 -- .../connected-site-row/index.js | 1 - .../connected-site-row/index.scss | 14 - .../connections-tab.component.js | 133 ----- .../connections-tab.container.js | 39 -- .../pages/settings/connections-tab/index.js | 1 - .../pages/settings/connections-tab/index.scss | 1 - .../add-contact/add-contact.component.js | 18 +- .../contact-list-tab.component.js | 49 +- .../edit-contact/edit-contact.component.js | 2 +- ui/app/pages/settings/index.scss | 13 - .../networks-tab/networks-tab.component.js | 34 +- ui/app/pages/settings/settings.component.js | 28 +- ui/app/selectors/selectors.js | 189 +++++++ ui/app/store/actions.js | 153 ++++-- ui/index.js | 22 +- ui/lib/icon-factory.js | 16 +- ui/lib/persistent-form.js | 4 +- yarn.lock | 214 +++++--- 466 files changed, 11451 insertions(+), 10273 deletions(-) create mode 100644 app/images/broken-line.svg create mode 100644 app/images/connect-white.svg rename app/images/{provider-approval-check.svg => permissions-check.svg} (100%) create mode 100644 app/scripts/controllers/permissions/index.js create mode 100644 app/scripts/controllers/permissions/loggerMiddleware.js create mode 100644 app/scripts/controllers/permissions/methodMiddleware.js create mode 100644 app/scripts/controllers/permissions/permissions-safe-methods.json create mode 100644 app/scripts/controllers/permissions/restrictedMethods.js delete mode 100644 app/scripts/controllers/provider-approval.js delete mode 100644 app/scripts/createStandardProvider.js create mode 100644 app/scripts/migrations/040.js create mode 100644 development/static-server.js create mode 100644 test/e2e/permissions.spec.js delete mode 100644 test/unit/app/controllers/provider-approval-test.js delete mode 100644 ui/app/components/app/bn-as-decimal-input.js create mode 100644 ui/app/components/app/connected-sites-list/connected-sites-list.component.js create mode 100644 ui/app/components/app/connected-sites-list/connected-sites-list.container.js create mode 100644 ui/app/components/app/connected-sites-list/index.js create mode 100644 ui/app/components/app/connected-sites-list/index.scss delete mode 100644 ui/app/components/app/copyable.js delete mode 100644 ui/app/components/app/customize-gas-modal/gas-slider.js delete mode 100644 ui/app/components/app/modals/account-modal-container.js create mode 100644 ui/app/components/app/modals/account-modal-container/account-modal-container.component.js create mode 100644 ui/app/components/app/modals/account-modal-container/account-modal-container.container.js create mode 100644 ui/app/components/app/modals/account-modal-container/index.js delete mode 100644 ui/app/components/app/modals/clear-approved-origins/clear-approved-origins.component.js delete mode 100644 ui/app/components/app/modals/clear-approved-origins/index.js delete mode 100644 ui/app/components/app/modals/deposit-ether-modal.js create mode 100644 ui/app/components/app/modals/deposit-ether-modal/deposit-ether-modal.component.js create mode 100644 ui/app/components/app/modals/deposit-ether-modal/deposit-ether-modal.container.js create mode 100644 ui/app/components/app/modals/deposit-ether-modal/index.js create mode 100644 ui/app/components/app/modals/disconnect-account/disconnect-account.component.js create mode 100644 ui/app/components/app/modals/disconnect-account/disconnect-account.container.js create mode 100644 ui/app/components/app/modals/disconnect-account/index.js create mode 100644 ui/app/components/app/modals/disconnect-account/index.scss create mode 100644 ui/app/components/app/modals/disconnect-all/disconnect-all.component.js rename ui/app/components/app/modals/{clear-approved-origins/clear-approved-origins.container.js => disconnect-all/disconnect-all.container.js} (53%) create mode 100644 ui/app/components/app/modals/disconnect-all/index.js create mode 100644 ui/app/components/app/modals/disconnect-all/index.scss delete mode 100644 ui/app/components/app/modals/export-private-key-modal.js create mode 100644 ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.component.js create mode 100644 ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.container.js create mode 100644 ui/app/components/app/modals/export-private-key-modal/index.js create mode 100644 ui/app/components/app/modals/new-account-modal/index.js create mode 100644 ui/app/components/app/modals/new-account-modal/index.scss create mode 100644 ui/app/components/app/modals/new-account-modal/new-account-modal.component.js create mode 100644 ui/app/components/app/modals/new-account-modal/new-account-modal.container.js create mode 100644 ui/app/components/app/permission-page-container/index.js create mode 100644 ui/app/components/app/permission-page-container/index.scss create mode 100644 ui/app/components/app/permission-page-container/permission-page-container-content/index.js create mode 100644 ui/app/components/app/permission-page-container/permission-page-container-content/permission-page-container-content.component.js create mode 100644 ui/app/components/app/permission-page-container/permission-page-container-header/index.js rename ui/app/components/app/{provider-page-container/provider-page-container-header/provider-page-container-header.component.js => permission-page-container/permission-page-container-header/permission-page-container-header.component.js} (58%) create mode 100644 ui/app/components/app/permission-page-container/permission-page-container.component.js create mode 100644 ui/app/components/app/permission-page-container/permission-page-container.container.js delete mode 100644 ui/app/components/app/provider-page-container/index.js delete mode 100644 ui/app/components/app/provider-page-container/index.scss delete mode 100644 ui/app/components/app/provider-page-container/provider-page-container-content/index.js delete mode 100644 ui/app/components/app/provider-page-container/provider-page-container-content/provider-page-container-content.component.js delete mode 100644 ui/app/components/app/provider-page-container/provider-page-container-content/provider-page-container-content.container.js delete mode 100644 ui/app/components/app/provider-page-container/provider-page-container-header/index.js delete mode 100644 ui/app/components/app/provider-page-container/provider-page-container.component.js delete mode 100644 ui/app/components/app/shift-list-item.js create mode 100644 ui/app/components/app/shift-list-item/index.js create mode 100644 ui/app/components/app/shift-list-item/shift-list-item.component.js create mode 100644 ui/app/components/app/shift-list-item/shift-list-item.container.js delete mode 100644 ui/app/components/app/signature-request-original.js create mode 100644 ui/app/components/app/signature-request-original/index.js create mode 100644 ui/app/components/app/signature-request-original/signature-request-original.component.js create mode 100644 ui/app/components/app/signature-request-original/signature-request-original.container.js delete mode 100644 ui/app/components/app/token-cell.js create mode 100644 ui/app/components/app/token-cell/index.js create mode 100644 ui/app/components/app/token-cell/token-cell.component.js create mode 100644 ui/app/components/app/token-cell/token-cell.container.js delete mode 100644 ui/app/components/app/wallet-view.js create mode 100644 ui/app/components/app/wallet-view/index.js create mode 100644 ui/app/components/app/wallet-view/wallet-view.component.js create mode 100644 ui/app/components/app/wallet-view/wallet-view.container.js delete mode 100644 ui/app/components/ui/eth-balance.js create mode 100644 ui/app/components/ui/eth-balance/eth-balance.component.js create mode 100644 ui/app/components/ui/eth-balance/eth-balance.container.js create mode 100644 ui/app/components/ui/eth-balance/index.js create mode 100644 ui/app/components/ui/icon-with-fallback/icon-with-fallback.component.js create mode 100644 ui/app/components/ui/icon-with-fallback/index.js create mode 100644 ui/app/components/ui/icon-with-fallback/index.scss create mode 100644 ui/app/components/ui/snackbar/index.js create mode 100644 ui/app/components/ui/snackbar/index.scss create mode 100644 ui/app/components/ui/snackbar/snackbar.component.js rename ui/app/css/itcss/components/pages/{provider-approval.scss => permission-approval.scss} (66%) create mode 100644 ui/app/pages/connected-sites/connected-sites.component.js create mode 100644 ui/app/pages/connected-sites/index.js create mode 100644 ui/app/pages/connected-sites/index.scss delete mode 100644 ui/app/pages/create-account/import-account/seed.js create mode 100644 ui/app/pages/first-time-flow/onboarding-initiator-util.js create mode 100644 ui/app/pages/mobile-sync/mobile-sync.component.js create mode 100644 ui/app/pages/mobile-sync/mobile-sync.container.js create mode 100644 ui/app/pages/permissions-connect/choose-account/choose-account.component.js create mode 100644 ui/app/pages/permissions-connect/choose-account/index.js create mode 100644 ui/app/pages/permissions-connect/choose-account/index.scss create mode 100644 ui/app/pages/permissions-connect/index.js create mode 100644 ui/app/pages/permissions-connect/index.scss create mode 100644 ui/app/pages/permissions-connect/permissions-connect-footer/index.js create mode 100644 ui/app/pages/permissions-connect/permissions-connect-footer/index.scss create mode 100644 ui/app/pages/permissions-connect/permissions-connect-footer/permissions-connect-footer.component.js create mode 100644 ui/app/pages/permissions-connect/permissions-connect-header/index.js create mode 100644 ui/app/pages/permissions-connect/permissions-connect-header/index.scss create mode 100644 ui/app/pages/permissions-connect/permissions-connect-header/permissions-connect-header.component.js create mode 100644 ui/app/pages/permissions-connect/permissions-connect.component.js create mode 100644 ui/app/pages/permissions-connect/permissions-connect.container.js delete mode 100644 ui/app/pages/provider-approval/index.js delete mode 100644 ui/app/pages/provider-approval/provider-approval.component.js delete mode 100644 ui/app/pages/provider-approval/provider-approval.container.js delete mode 100644 ui/app/pages/send/to-autocomplete.component.js delete mode 100644 ui/app/pages/send/to-autocomplete/index.js delete mode 100644 ui/app/pages/send/to-autocomplete/to-autocomplete.js delete mode 100644 ui/app/pages/settings/connections-tab/connected-site-row/connected-site-row.component.js delete mode 100644 ui/app/pages/settings/connections-tab/connected-site-row/index.js delete mode 100644 ui/app/pages/settings/connections-tab/connected-site-row/index.scss delete mode 100644 ui/app/pages/settings/connections-tab/connections-tab.component.js delete mode 100644 ui/app/pages/settings/connections-tab/connections-tab.container.js delete mode 100644 ui/app/pages/settings/connections-tab/index.js delete mode 100644 ui/app/pages/settings/connections-tab/index.scss diff --git a/.circleci/config.yml b/.circleci/config.yml index af3b70ebb..5270da98f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -22,6 +22,7 @@ workflows: - test-lint: requires: - prep-deps + - test-lint-shellcheck - test-e2e-chrome: requires: - prep-deps @@ -49,6 +50,7 @@ workflows: - all-tests-pass: requires: - test-lint + - test-lint-shellcheck - test-unit - test-unit-global - test-mozilla-lint @@ -173,6 +175,16 @@ jobs: name: Verify locales command: yarn verify-locales --quiet + test-lint-shellcheck: + docker: + - image: circleci/node:10.17-browsers + steps: + - checkout + - run: sudo apt-get install shellcheck + - run: + name: Shellcheck Lint + command: yarn lint:shellcheck + test-deps: docker: - image: circleci/node:10.17-browsers diff --git a/.eslintrc b/.eslintrc index 08d8aeb9d..74b30dcbe 100644 --- a/.eslintrc +++ b/.eslintrc @@ -44,18 +44,19 @@ }, "rules": { + "default-case": 2, "import/no-unresolved": ["error", { "commonjs": true }], "no-restricted-globals": ["error", "event"], "accessor-pairs": 2, "arrow-spacing": [2, { "before": true, "after": true }], "block-spacing": [2, "always"], - "brace-style": [2, "1tbs", { "allowSingleLine": true }], + "brace-style": 2, "camelcase": [2, { "properties": "never" }], "comma-dangle": [2, "always-multiline"], "comma-spacing": [2, { "before": false, "after": true }], "comma-style": [2, "last"], "constructor-super": 2, - "curly": [2, "multi-line"], + "curly": 2, "dot-location": [2, "property"], "eol-last": 2, "eqeqeq": [2, "allow-null"], @@ -142,17 +143,38 @@ "no-useless-computed-key": 2, "no-useless-constructor": 2, "no-useless-escape": 2, + "no-var": 2, "no-whitespace-before-property": 2, "no-with": 2, "one-var": [2, { "initialized": "never" }], "operator-linebreak": [2, "after", { "overrides": { "?": "ignore", ":": "ignore" } }], "padded-blocks": "off", "quotes": [2, "single", {"avoidEscape": true, "allowTemplateLiterals": true}], + "react/no-unused-state": 2, "react/jsx-boolean-value": 2, "react/jsx-curly-brace-presence": [2, { "props": "never", "children": "never" }], "react/jsx-equals-spacing": 2, "react/no-deprecated": 0, "react/default-props-match-prop-types": 2, + "react/jsx-closing-tag-location": 2, + "react/jsx-no-duplicate-props": 2, + "react/jsx-closing-bracket-location": 2, + "react/jsx-first-prop-new-line": 2, + "react/jsx-max-props-per-line": [2, { "maximum": 1, "when": "multiline"} ], + "react/jsx-tag-spacing": [2, { + "closingSlash": "never", + "beforeSelfClosing": "always", + "afterOpening": "never" + }], + "react/jsx-wrap-multilines": [2, { + "declaration": "parens-new-line", + "assignment": "parens-new-line", + "return": "parens-new-line", + "arrow": "parens-new-line", + "condition": "parens-new-line", + "logical": "parens-new-line", + "prop": "parens-new-line" + }], "semi": [2, "never"], "semi-spacing": [2, { "before": false, "after": true }], "space-before-blocks": [2, "always"], diff --git a/.gitattributes b/.gitattributes index 590ac71c0..561741e37 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,4 @@ +* text=auto CHANGELOG.md merge=union # Reviewing the lockfile contents is an important step in verifying that diff --git a/CHANGELOG.md b/CHANGELOG.md index b114a57e3..006e4ae29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,6 +67,7 @@ - [#7558](https://github.com/MetaMask/metamask-extension/pull/7558): Use localized messages for NotificationModal buttons ## 7.7.0 Thu Nov 28 2019 [WITHDRAWN] +## 7.7.0 Thu Nov 28 2019 - [#7004](https://github.com/MetaMask/metamask-extension/pull/7004): Connect distinct accounts per site - [#7480](https://github.com/MetaMask/metamask-extension/pull/7480): Fixed link on root README.md - [#7482](https://github.com/MetaMask/metamask-extension/pull/7482): Update Wyre ETH purchase url diff --git a/README.md b/README.md index 08d7e3a74..c1db416e3 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,6 @@ To write tests that will be run in the browser using QUnit, add your test files - [How to add new networks to the Provider Menu](./docs/adding-new-networks.md) - [How to port MetaMask to a new platform](./docs/porting_to_new_environment.md) - [How to use the TREZOR emulator](./docs/trezor-emulator.md) -- [How to generate a visualization of this repository's development](./docs/development-visualization.md) +- [How to generate a visualization of this repository's development](./development/gource-viz.sh) [1]: http://www.nomnoml.com/#view/%5B%3Cactor%3Euser%5D%0A%0A%5Bmetamask-ui%7C%0A%20%20%20%5Btools%7C%0A%20%20%20%20%20react%0A%20%20%20%20%20redux%0A%20%20%20%20%20thunk%0A%20%20%20%20%20ethUtils%0A%20%20%20%20%20jazzicon%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20account-detail%0A%20%20%20%20%20accounts%0A%20%20%20%20%20locked-screen%0A%20%20%20%20%20restore-vault%0A%20%20%20%20%20identicon%0A%20%20%20%20%20config%0A%20%20%20%20%20info%0A%20%20%20%5D%0A%20%20%20%5Breducers%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20metamask%0A%20%20%20%20%20identities%0A%20%20%20%5D%0A%20%20%20%5Bactions%7C%0A%20%20%20%20%20%5BbackgroundConnection%5D%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%5D%3A-%3E%5Bactions%5D%0A%20%20%20%5Bactions%5D%3A-%3E%5Breducers%5D%0A%20%20%20%5Breducers%5D%3A-%3E%5Bcomponents%5D%0A%5D%0A%0A%5Bweb%20dapp%7C%0A%20%20%5Bui%20code%5D%0A%20%20%5Bweb3%5D%0A%20%20%5Bmetamask-inpage%5D%0A%20%20%0A%20%20%5B%3Cactor%3Eui%20developer%5D%0A%20%20%5Bui%20developer%5D-%3E%5Bui%20code%5D%0A%20%20%5Bui%20code%5D%3C-%3E%5Bweb3%5D%0A%20%20%5Bweb3%5D%3C-%3E%5Bmetamask-inpage%5D%0A%5D%0A%0A%5Bmetamask-background%7C%0A%20%20%5Bprovider-engine%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bid%20store%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%3E%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%3C-%3E%5Bid%20store%5D%0A%20%20%5Bconfig%20manager%7C%0A%20%20%20%20%5Brpc%20configuration%5D%0A%20%20%20%20%5Bencrypted%20keys%5D%0A%20%20%20%20%5Bwallet%20nicknames%5D%0A%20%20%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%5Bconfig%20manager%5D%0A%20%20%5Bid%20store%5D%3C-%3E%5Bconfig%20manager%5D%0A%5D%0A%0A%5Buser%5D%3C-%3E%5Bmetamask-ui%5D%0A%0A%5Buser%5D%3C%3A--%3A%3E%5Bweb%20dapp%5D%0A%0A%5Bmetamask-contentscript%7C%0A%20%20%5Bplugin%20restart%20detector%5D%0A%20%20%5Brpc%20passthrough%5D%0A%5D%0A%0A%5Brpc%20%7C%0A%20%20%5Bethereum%20blockchain%20%7C%0A%20%20%20%20%5Bcontracts%5D%0A%20%20%20%20%5Baccounts%5D%0A%20%20%5D%0A%5D%0A%0A%5Bweb%20dapp%5D%3C%3A--%3A%3E%5Bmetamask-contentscript%5D%0A%5Bmetamask-contentscript%5D%3C-%3E%5Bmetamask-background%5D%0A%5Bmetamask-background%5D%3C-%3E%5Bmetamask-ui%5D%0A%5Bmetamask-background%5D%3C-%3E%5Brpc%5D%0A diff --git a/app/_locales/am/messages.json b/app/_locales/am/messages.json index 08b237aa0..1b9026d4c 100644 --- a/app/_locales/am/messages.json +++ b/app/_locales/am/messages.json @@ -1,28 +1,16 @@ { - "privacyModeDefault": { - "message": "የግላዊነት ኩነት አሁን በንቡር ነቅቷል" - }, "chartOnlyAvailableEth": { "message": "ቻርት የሚገኘው በ Ethereum አውታረ መረቦች ላይ ብቻ ነው።" }, - "confirmClear": { - "message": "የተፈቀዱ ድረ ገጾችን ለማጥራት እንደሚፈልጉ እርግጠኛ ነዎት?" - }, "contractInteraction": { "message": "የግንኙነት ተግባቦት" }, - "clearApprovalData": { - "message": "የግላዊነት ውሂብን አጥራ" - }, "reject": { "message": "አይቀበሉ" }, - "providerRequest": { + "likeToConnect": { "message": "$1ከመለያዎ ጋር ለመገናኘት ይፈልጋል" }, - "providerRequestInfo": { - "message": "ይህ ድረ ገጽ የእርስዎን መለያ ወቅታዊ አድራሻ ለማየት እየጠየቀ ነው። ምንጊዜም ግንኙነት የሚያደርጉባቸውን ድረ ገጾች የሚያምኗቸው መሆኑን ያረጋግጡ።" - }, "about": { "message": "ስለ" }, @@ -157,10 +145,6 @@ "basic": { "message": "መሠረታዊ" }, - "betweenMinAndMax": { - "message": "ከ$1መብለጥ ወይም እኩል እና ከ$2በታች ወይም እኩል መሆን አለበት።", - "description": "helper for inputting hex as decimal input" - }, "blockExplorerUrl": { "message": "ኤክስፕሎረር አግድ" }, @@ -252,9 +236,6 @@ "connect": { "message": "ይገናኙ" }, - "connectRequest": { - "message": "የግንኙነት ጥያቄ" - }, "connectingTo": { "message": "ከ $1ጋር መገናኘት" }, @@ -294,9 +275,6 @@ "copiedExclamation": { "message": "ተቀድቷል" }, - "copy": { - "message": "ቅዳ" - }, "copyAddress": { "message": "አድራሻን ወደ ቅንጥብ ሰሌዳ ቅዳ" }, @@ -375,9 +353,6 @@ "directDepositEtherExplainer": { "message": "ቀደም ሲል የተወሰነ Ether ካለዎት፣ በአዲሱ ቋትዎ Ether ለማግኘት ፈጣኑ መንገድ ቀጥተኛ ተቀማጭ ነው።" }, - "dismiss": { - "message": "አሰናብት" - }, "done": { "message": "ተጠናቅቋል" }, @@ -543,10 +518,6 @@ "getStarted": { "message": "አስጀማሪ መመሪያ" }, - "greaterThanMin": { - "message": "ከ $1ጋር እኩል መሆን ወይም መብለጥ አለበት።", - "description": "helper for inputting hex as decimal input" - }, "happyToSeeYou": { "message": "እርስዎን በማየታችን ደስተኛ ነን።" }, @@ -665,10 +636,6 @@ "ledgerAccountRestriction": { "message": "አዲስ መለያ ከማከልዎ በፊት የመጨረሻውን መለያዎን መጠቀም አለብዎት።" }, - "lessThanMax": { - "message": "ከ $1ያነሰ ወይም እኩል መሆን አለበት።", - "description": "helper for inputting hex as decimal input" - }, "letsGoSetUp": { "message": "አዎ፣ እናደራጅ!" }, @@ -867,9 +834,6 @@ "message": "የግል ቁልፍዎን ሕብረ ቁምፊ እዚህ ለጥፍ፡", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "የዘር ሐረግዎን እዚህ ይለጥፉ!" - }, "pending": { "message": "በእንጥልጥል ላይ ያለ" }, @@ -1183,9 +1147,6 @@ "storePhrase": { "message": "ይህን ሐረግ እንደ 1Password ባለ የይለፍ ቃል አስተዳዳሪ ውስጥ ያስቀምጡ።" }, - "submit": { - "message": "አስገባ" - }, "submitted": { "message": "የቀረበ" }, @@ -1353,9 +1314,6 @@ "userName": { "message": "የተጣቃሚ ስም" }, - "validFileImport": { - "message": "የሚያስመጡትን ትክክለኛ ፋይል መምረጥ አለብዎ፡" - }, "viewAccount": { "message": "መለያን ይመልከቱ" }, diff --git a/app/_locales/ar/messages.json b/app/_locales/ar/messages.json index 18b9eafa2..e686d498c 100644 --- a/app/_locales/ar/messages.json +++ b/app/_locales/ar/messages.json @@ -1,28 +1,16 @@ { - "privacyModeDefault": { - "message": "يتم تمكين وضع الخصوصية الآن بشكل افتراضي" - }, "chartOnlyAvailableEth": { "message": "الرسم البياني متاح فقط على شبكات إيثيريوم." }, - "confirmClear": { - "message": "هل أنت متأكد من أنك تريد مسح المواقع المعتمدة؟" - }, "contractInteraction": { "message": "التفاعل على العقد" }, - "clearApprovalData": { - "message": "مسح بيانات الخصوصية" - }, "reject": { "message": "رفض" }, - "providerRequest": { + "likeToConnect": { "message": "يرغب $1 في الاتصال بحسابك" }, - "providerRequestInfo": { - "message": "يطلب هذا الموقع حق الوصول لعرض عنوان حسابك الحالي. تأكد دائماً من ثقتك في المواقع التي تتفاعل معها." - }, "about": { "message": "حول" }, @@ -157,10 +145,6 @@ "basic": { "message": "الأساسية" }, - "betweenMinAndMax": { - "message": "يجب أن تكون أكبر من أو تساوي $1 وأقل من أو تساوي $2.", - "description": "helper for inputting hex as decimal input" - }, "blockExplorerUrl": { "message": "متصفح Block Explorer" }, @@ -252,9 +236,6 @@ "connect": { "message": "اتصال" }, - "connectRequest": { - "message": "طلب اتصال" - }, "connectingTo": { "message": "جارِ الاتصال بـ $1" }, @@ -294,9 +275,6 @@ "copiedExclamation": { "message": "تم النسخ." }, - "copy": { - "message": "نسخ" - }, "copyAddress": { "message": "نسخ العنوان إلى الحافظة" }, @@ -375,9 +353,6 @@ "directDepositEtherExplainer": { "message": "إذا كان لديك بالفعل بعض الأثير، فإن أسرع طريقة للحصول على الأثير في محفظتك الجديدة عن طريق الإيداع المباشر." }, - "dismiss": { - "message": "رفض" - }, "done": { "message": "تم" }, @@ -539,10 +514,6 @@ "getStarted": { "message": "البدء" }, - "greaterThanMin": { - "message": "يجب أن يكون أكبر من أو يساوي $1.", - "description": "helper for inputting hex as decimal input" - }, "happyToSeeYou": { "message": "نحن سعداء برؤيتك." }, @@ -658,10 +629,6 @@ "ledgerAccountRestriction": { "message": "أنت بحاجة إلى استخدام حسابك الأخير قبل أن تتمكن من إضافة حساب جديد." }, - "lessThanMax": { - "message": "يجب أن يكون أقل من أو يساوي $1.", - "description": "helper for inputting hex as decimal input" - }, "letsGoSetUp": { "message": "نعم، دعنا نبدأ التثبيت!" }, @@ -863,9 +830,6 @@ "message": "الصق مقطع مفتاحك الخاص هنا:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "قم بلصق عبارة الأمان الخاصة بك هنا!" - }, "pending": { "message": "قيد الانتظار" }, @@ -1179,9 +1143,6 @@ "storePhrase": { "message": "احتفظ بهذه الجملة في مدير كلمات مرور مثل 1Password." }, - "submit": { - "message": "إرسال" - }, "submitted": { "message": "المقدمة" }, @@ -1349,9 +1310,6 @@ "userName": { "message": "اسم المستخدم" }, - "validFileImport": { - "message": "يجب عليك تحديد ملف صالح للاستيراد." - }, "viewAccount": { "message": "عرض حساب" }, diff --git a/app/_locales/bg/messages.json b/app/_locales/bg/messages.json index 799214110..249e1c779 100644 --- a/app/_locales/bg/messages.json +++ b/app/_locales/bg/messages.json @@ -1,33 +1,21 @@ { - "privacyModeDefault": { - "message": "Режимът на поверителност вече е активиран по подразбиране" - }, "chartOnlyAvailableEth": { "message": "Диаграмата е достъпна само в мрежи на Ethereum." }, - "confirmClear": { - "message": "Сигурни ли сте, че искате да изчистите одобрените уебсайтове?" - }, "contractInteraction": { "message": "Взаимодействие с договор" }, - "clearApprovalData": { - "message": "Изчистване на данните за поверителност" - }, "reject": { "message": "Отхвърляне" }, - "providerRequest": { + "likeToConnect": { "message": "$1 би искал да се свърже с вашия акаунт" }, - "providerRequestInfo": { - "message": "Този сайт иска достъп за преглед на адреса на текущия ви акаунт. Винаги се уверявайте, че се доверявате на сайтовете, с които взаимодействате." - }, "about": { "message": "Информация" }, "aboutSettingsDescription": { - "message": "Версия, център за поддръжка и информация за контакт." + "message": "Версия, център за поддръжка и информация за контакт" }, "acceleratingATransaction": { "message": "* Ускоряването на транзакция чрез използване на по-висока цена на газа увеличава шансовете й да се обработва по-бързо от мрежата, но това не винаги е гарантирано." @@ -63,7 +51,7 @@ "message": "Разширени" }, "advancedSettingsDescription": { - "message": "Достъп до функции за разработчици, изтегляйте дневници, нулиране на акаунта, тестови мрежи за настройка и персонализиран RPC." + "message": "Достъп до функции за разработчици, изтегляйте дневници, нулиране на акаунта, тестови мрежи за настройка и персонализиран RPC" }, "advancedOptions": { "message": "Разширени опции" @@ -157,10 +145,6 @@ "basic": { "message": "Основни" }, - "betweenMinAndMax": { - "message": "трябва да бъде по-голям или равен на $1 и по-малък или равен на $2.", - "description": "helper for inputting hex as decimal input" - }, "blockExplorerUrl": { "message": "Блокиране на Explorer" }, @@ -252,9 +236,6 @@ "connect": { "message": "Свързване" }, - "connectRequest": { - "message": "Свържете заявка" - }, "connectingTo": { "message": "Свързване с $1" }, @@ -294,9 +275,6 @@ "copiedExclamation": { "message": "Копирано!" }, - "copy": { - "message": "Копиране" - }, "copyAddress": { "message": "Копирайте адреса в клипборда" }, @@ -375,9 +353,6 @@ "directDepositEtherExplainer": { "message": "Ако вече имате някакъв етер, най-бързият начин да получите етер в новия си портфейл е чрез директен депозит." }, - "dismiss": { - "message": "Отхвърляне" - }, "done": { "message": "Готово" }, @@ -539,10 +514,6 @@ "getStarted": { "message": "Първи стъпки" }, - "greaterThanMin": { - "message": "трябва да бъде по-голяма или равна на $1.", - "description": "helper for inputting hex as decimal input" - }, "happyToSeeYou": { "message": "Радваме се да Ви видим." }, @@ -661,10 +632,6 @@ "ledgerAccountRestriction": { "message": "Трябва да използвате последния си акаунт, преди да можете да добавите нов." }, - "lessThanMax": { - "message": "трябва да бъде по-малко или равно на $1.", - "description": "helper for inputting hex as decimal input" - }, "letsGoSetUp": { "message": "Да, нека да настроим нещата!" }, @@ -866,9 +833,6 @@ "message": "Поставете низ от личния си ключ тук:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "Поставете тук ключовата си фраза!" - }, "pending": { "message": "в изчакване" }, @@ -1182,9 +1146,6 @@ "storePhrase": { "message": "Съхранявайте тази фраза в мениджър на пароли като 1Password." }, - "submit": { - "message": "Изпращане" - }, "submitted": { "message": "Изпратен" }, @@ -1352,9 +1313,6 @@ "userName": { "message": "Потребителско име" }, - "validFileImport": { - "message": "Трябва да изберете валиден файл, който да импортирате." - }, "viewAccount": { "message": "Преглед на профила" }, diff --git a/app/_locales/bn/messages.json b/app/_locales/bn/messages.json index de1c0b6da..ae36440da 100644 --- a/app/_locales/bn/messages.json +++ b/app/_locales/bn/messages.json @@ -1,28 +1,16 @@ { - "privacyModeDefault": { - "message": "গোপনীয়তার মোড এখন ডিফল্ট হিসাবে সক্রিয় করা আছে" - }, "chartOnlyAvailableEth": { "message": "শুধুমাত্র Ethereum নেটওয়ার্কগুলিতে চার্ট উপলভ্য। " }, - "confirmClear": { - "message": "আপনি কি অনুমোদিত ওয়েবসাইটগুলি মুছে পরিস্কার করার বিষয়ে নিশ্চিত?" - }, "contractInteraction": { "message": "কন্ট্র্যাক্ট বাক্যালাপ" }, - "clearApprovalData": { - "message": "গোপনীয়তার ডেটা মুছে পরিস্কার করুন" - }, "reject": { "message": "প্রত্যাখ্যান" }, - "providerRequest": { + "likeToConnect": { "message": "$1 আপনার অ্যাকাউন্টের সাথে সংযোগ করতে চায়" }, - "providerRequestInfo": { - "message": "এই সাইটটি আপনার বর্তমান অ্যাকাউন্টের ঠিকানা দেখার অ্যাক্সেসের জন্য অনুরোধ জানাচ্ছে। সবসময় নিশ্চিত হয়ে নেবেন যে আপনি যে সাইটের সাথে যোগাযোগ করছেন সেটি বিশ্বাসযোগ্য কিনা।" - }, "about": { "message": "সম্পর্কে" }, @@ -157,10 +145,6 @@ "basic": { "message": "প্রাথমিক" }, - "betweenMinAndMax": { - "message": "অবশ্যই $1 এর বড় বা সমান এবং $2 এর ছোটো বা সমান হতে হবে।", - "description": "helper for inputting hex as decimal input" - }, "blockExplorerUrl": { "message": "এক্সপ্লোরার ব্লক করুন" }, @@ -252,9 +236,6 @@ "connect": { "message": "সংযুক্ত করুন" }, - "connectRequest": { - "message": "সংযোগের অনুরোধ" - }, "connectingTo": { "message": " $1 এর সাথে সংযোগ করছে" }, @@ -294,9 +275,6 @@ "copiedExclamation": { "message": "কপি করা হয়েছে!" }, - "copy": { - "message": "অনুলিপি" - }, "copyAddress": { "message": "ক্লিপবোর্ডে ঠিকানা কপি করুন" }, @@ -375,9 +353,6 @@ "directDepositEtherExplainer": { "message": "আপনার ইতিমধ্যে কিছু ইথার থেকে থাকলে আপনার নতুন ওয়ালেটে ইথার পাওয়ার দ্রুততম উপায় হল সরাসরি জমা করা।" }, - "dismiss": { - "message": "খারিজ" - }, "done": { "message": "সম্পন্ন " }, @@ -543,10 +518,6 @@ "getStarted": { "message": "শুরু করুন" }, - "greaterThanMin": { - "message": "অবশ্যই $1 এর থেকে বড় বা সমান হতে হবে।", - "description": "helper for inputting hex as decimal input" - }, "happyToSeeYou": { "message": "আপনাকে দেখে আমরা আনন্দিত।" }, @@ -665,10 +636,6 @@ "ledgerAccountRestriction": { "message": "একটি নতুন অ্যা কাউন্ট যোগ করার আগে আপনাকে আপনার শেষ অ্যাকাউন্ট ব্যবহার করে ফেলতে হবে।" }, - "lessThanMax": { - "message": "অবশ্যই $1 এর থেকে কম বা সমান হতে হবে।", - "description": "helper for inputting hex as decimal input" - }, "letsGoSetUp": { "message": "হ্যাঁ, তাহলে সেট আপ করে নেওয়া যাক!" }, @@ -870,9 +837,6 @@ "message": "আপনার গোপনীয় কী স্ট্রিং এখানে পেস্ট করুন:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "আপনার সীড ফ্রেজ এখানে পেস্ট করুন!" - }, "pending": { "message": "বাকি" }, @@ -1186,9 +1150,6 @@ "storePhrase": { "message": "এই বাক্যাংশটি 1Password এর মতো একটি পাসওয়ার্ড পরিচালকে সংরক্ষণ করুন। " }, - "submit": { - "message": "জমা দিন" - }, "submitted": { "message": "জমা করা হয়েছে" }, @@ -1356,9 +1317,6 @@ "userName": { "message": "ইউজারনেম" }, - "validFileImport": { - "message": "আমদানি করার জন্য আপনাকে অবশ্যই একটি বৈধ ফাইল নির্বাচন করতে হবে।" - }, "viewAccount": { "message": "আ্যাকাউন্ট দেখুন" }, diff --git a/app/_locales/ca/messages.json b/app/_locales/ca/messages.json index 599cb84ad..4c131ef09 100644 --- a/app/_locales/ca/messages.json +++ b/app/_locales/ca/messages.json @@ -1,33 +1,21 @@ { - "privacyModeDefault": { - "message": "El mode de privacitat ara està activat per defecte" - }, "chartOnlyAvailableEth": { "message": "Mostra només els disponibles a les xarxes Ethereum." }, - "confirmClear": { - "message": "Estàs segur que vols eliminar totes les pàgines web aprovades?" - }, "contractInteraction": { "message": "Contractar Interacció" }, - "clearApprovalData": { - "message": "Elimina les dades de privacitat" - }, "reject": { "message": "Rebutja" }, - "providerRequest": { + "likeToConnect": { "message": "a $1 li agradaria connectar-se al teu compte" }, - "providerRequestInfo": { - "message": "Aquesta pàgina està demanant accès a la teva adreça" - }, "about": { "message": "Informació" }, "aboutSettingsDescription": { - "message": "Versió, centre de suport, i informació de contacte." + "message": "Versió, centre de suport, i informació de contacte" }, "acceleratingATransaction": { "message": "* Accelerar una transacció utilitzant un preu de gas més alt augmenta les possibilitats de ser processat més ràpidament per la xarxa, però no sempre es pot garantir." @@ -63,7 +51,7 @@ "message": "Configuració avançada" }, "advancedSettingsDescription": { - "message": "Accedeix a característiques de desenvolupador, descarrega Registres d'Estat, Reinicia el Compte, instal·la testnets i personalitza RPC." + "message": "Accedeix a característiques de desenvolupador, descarrega Registres d'Estat, Reinicia el Compte, instal·la testnets i personalitza RPC" }, "advancedOptions": { "message": "Opcions Avançades" @@ -157,10 +145,6 @@ "basic": { "message": "Opcions bàsiques" }, - "betweenMinAndMax": { - "message": "ha de ser igual o superior a $1 i menor o igual a $2.", - "description": "helper for inputting hex as decimal input" - }, "blockExplorerUrl": { "message": "Bloqueja l'explorador" }, @@ -249,9 +233,6 @@ "connect": { "message": "Connecta" }, - "connectRequest": { - "message": "Sol·licitud de connexió" - }, "connectingTo": { "message": "Connectant a $1 " }, @@ -291,9 +272,6 @@ "copiedExclamation": { "message": "S'ha copiat!" }, - "copy": { - "message": "Copia" - }, "copyAddress": { "message": "Copiar adreça al porta-retalls" }, @@ -372,9 +350,6 @@ "directDepositEtherExplainer": { "message": "Si ja tens una mica d'Ether, la manera més ràpida de posar Ether al teu nou moneder és per dipòsit directe." }, - "dismiss": { - "message": "Omet" - }, "done": { "message": "Fet" }, @@ -533,10 +508,6 @@ "getStarted": { "message": "Comença" }, - "greaterThanMin": { - "message": "ha de ser major o igual a $1.", - "description": "helper for inputting hex as decimal input" - }, "happyToSeeYou": { "message": "Ens alegrem de veure't." }, @@ -649,10 +620,6 @@ "ledgerAccountRestriction": { "message": "Has de fer servir el teu últim compte abans de poder afegir-ne un altre." }, - "lessThanMax": { - "message": "ha de ser igual o menor a $1 .", - "description": "helper for inputting hex as decimal input" - }, "letsGoSetUp": { "message": "Sí, posem-nos en marxa!" }, @@ -702,7 +669,7 @@ "message": "Conectant-te a Ethereum i la web descentralitzada." }, "metamaskSeedWords": { - "message": "Frase de recuperació de Metamask" + "message": "Frase de recuperació de MetaMask" }, "metamaskVersion": { "message": "Versió MetaMask" @@ -854,9 +821,6 @@ "message": "Enganxa la teva cadena clau privada aquí:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "Enganxa la teva frase de seeds aquí!" - }, "pending": { "message": "pendent" }, @@ -1164,9 +1128,6 @@ "storePhrase": { "message": "Guarda aquesta frase a un gestor de contrasenyes com Contrasenya 1" }, - "submit": { - "message": "Envia" - }, "submitted": { "message": "Enviat" }, @@ -1325,9 +1286,6 @@ "userName": { "message": "Nom d'usuari" }, - "validFileImport": { - "message": "Has de seleccionar un arxiu vàlid per a importar." - }, "viewAccount": { "message": "Mostra el compte" }, diff --git a/app/_locales/cs/messages.json b/app/_locales/cs/messages.json index cd768d4d8..9d04ae048 100644 --- a/app/_locales/cs/messages.json +++ b/app/_locales/cs/messages.json @@ -1,16 +1,7 @@ { - "confirmClear": { - "message": "Naozaj chcete vymazať schválené webové stránky?" - }, - "clearApprovalData": { - "message": "Jasné údaje o schválení" - }, "reject": { "message": "Odmítnout" }, - "providerRequestInfo": { - "message": "Níže uvedená doména se pokouší požádat o přístup k API Ethereum, aby mohla komunikovat s blokádou Ethereum. Před schválením přístupu Ethereum vždy zkontrolujte, zda jste na správném místě." - }, "account": { "message": "Účet" }, @@ -58,10 +49,6 @@ "balanceIsInsufficientGas": { "message": "Nedostatek prostředků pro aktuální množství paliva" }, - "betweenMinAndMax": { - "message": "musí být větší nebo roven $1 a menší nebo roven $2.", - "description": "helper for inputting hex as decimal input" - }, "blockiesIdenticon": { "message": "Použít Blockies Identicon" }, @@ -113,9 +100,6 @@ "copiedExclamation": { "message": "Zkopírováno!" }, - "copy": { - "message": "Kopírovat" - }, "copyToClipboard": { "message": "Kopírovat do schránky" }, @@ -227,10 +211,6 @@ "message": "Získejte Ether z faucetu za $1.", "description": "Displays network name for Ether faucet" }, - "greaterThanMin": { - "message": "musí být větší nebo roven $1.", - "description": "helper for inputting hex as decimal input" - }, "here": { "message": "zde", "description": "as in -click here- for more information (goes with troubleTokenBalances)" @@ -285,10 +265,6 @@ "learnMore": { "message": "Zjistěte více." }, - "lessThanMax": { - "message": "musí být menší nebo roven $1.", - "description": "helper for inputting hex as decimal input" - }, "likeToAddTokens": { "message": "Chcete přidat tyto tokeny?" }, @@ -367,9 +343,6 @@ "message": "Vložte zde svůj privátní klíč:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "Svou klíčovou frázi vložte zde!" - }, "personalAddressDetected": { "message": "Detekována osobní adresa. Zadejte adresu kontraktu tokenu." }, @@ -476,9 +449,6 @@ "stateLogError": { "message": "Chyba během získávání stavových protokolů." }, - "submit": { - "message": "Odeslat" - }, "submitted": { "message": "Odesláno" }, @@ -535,9 +505,6 @@ "usedByClients": { "message": "Používána různými klienty" }, - "validFileImport": { - "message": "Musíte vybrat validní soubor k importu." - }, "viewAccount": { "message": "Zobrazit účet" }, diff --git a/app/_locales/da/messages.json b/app/_locales/da/messages.json index 5a2c3c100..1c5fa4edb 100644 --- a/app/_locales/da/messages.json +++ b/app/_locales/da/messages.json @@ -1,33 +1,21 @@ { - "privacyModeDefault": { - "message": "Privatlivstilstand er nu som udgangspunkt aktiveret" - }, "chartOnlyAvailableEth": { "message": "Skema kun tilgængeligt på Ethereum-netværk." }, - "confirmClear": { - "message": "Er du sikker på, at du vil rydde godkendte hjemmesider?" - }, "contractInteraction": { "message": "Kontraktinteraktion" }, - "clearApprovalData": { - "message": "Ryd fortrolighedsdata" - }, "reject": { "message": "Afvis" }, - "providerRequest": { + "likeToConnect": { "message": "$1 ønsker at forbinde til din konto" }, - "providerRequestInfo": { - "message": "Denne side anmoder om at se din nuværende kontoadresse. Sørg altid for, at du stoler på de sider du interagerer med." - }, "about": { "message": "Om" }, "aboutSettingsDescription": { - "message": "Version, supportcenter og kontaktinformation." + "message": "Version, supportcenter og kontaktinformation" }, "acceleratingATransaction": { "message": "* At gøre din transaktion hurtigere ved at bruge en højere Gas-priser, øger dennes chancer for at blive behandlet af netværket hurtigere, men det er ikke altid garanteret." @@ -63,7 +51,7 @@ "message": "Avanceret" }, "advancedSettingsDescription": { - "message": "Få adgang til udviklerfunktioner, hent tilstandslogs, nulstil konto, opsæt testnetværk og brugerdefineret RPC." + "message": "Få adgang til udviklerfunktioner, hent tilstandslogs, nulstil konto, opsæt testnetværk og brugerdefineret RPC" }, "advancedOptions": { "message": "Avancerede Valgmuligheder" @@ -157,10 +145,6 @@ "basic": { "message": "Grundlæggende oplysninger" }, - "betweenMinAndMax": { - "message": "skal være større end eller lig med $1 og mindre end eller lig med $2.", - "description": "helper for inputting hex as decimal input" - }, "blockExplorerUrl": { "message": "Block-udforsker" }, @@ -252,9 +236,6 @@ "connect": { "message": "Få forbindelse" }, - "connectRequest": { - "message": "Tilslutningsanmodning" - }, "connectingTo": { "message": "Forbinder til $1" }, @@ -294,9 +275,6 @@ "copiedExclamation": { "message": "Kopieret!" }, - "copy": { - "message": "Kopiér" - }, "copyAddress": { "message": "Kopier adresse til udklipsholder" }, @@ -375,9 +353,6 @@ "directDepositEtherExplainer": { "message": "Hvis du allerede har Ether, er den hurtigste måde at få Ether i din nye tegnebog ved direkte indbetaling." }, - "dismiss": { - "message": "Luk" - }, "done": { "message": "Færdig" }, @@ -539,10 +514,6 @@ "getStarted": { "message": "Kom godt i gang" }, - "greaterThanMin": { - "message": "skal være større end eller lig med $1.", - "description": "helper for inputting hex as decimal input" - }, "happyToSeeYou": { "message": "Vi er glade for at se dig." }, @@ -658,10 +629,6 @@ "ledgerAccountRestriction": { "message": "Du skal benytte din tidligere konto, før du kan tilføje en ny." }, - "lessThanMax": { - "message": "skal være mindre end eller lig med $1.", - "description": "helper for inputting hex as decimal input" - }, "letsGoSetUp": { "message": "Ja, lad os komme i gang!" }, @@ -848,9 +815,6 @@ "message": "Indsæt din private nøglestreng her:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "Indsæt din seed-sætning her!" - }, "pending": { "message": "afventer" }, @@ -1161,9 +1125,6 @@ "storePhrase": { "message": "Gem denne sætning i en adgangskodeadministrator som 1Password." }, - "submit": { - "message": "Indsend" - }, "submitted": { "message": "Indsendt" }, @@ -1322,9 +1283,6 @@ "userName": { "message": "Brugernavn" }, - "validFileImport": { - "message": "Du skal vælge en gyldig fil at importere." - }, "viewAccount": { "message": "Vis konto" }, diff --git a/app/_locales/de/messages.json b/app/_locales/de/messages.json index 705d7f92e..83f44a32a 100644 --- a/app/_locales/de/messages.json +++ b/app/_locales/de/messages.json @@ -1,33 +1,21 @@ { - "privacyModeDefault": { - "message": "Der Datenschutzmodus ist jetzt standardmäßig aktiviert" - }, "chartOnlyAvailableEth": { "message": "Die Grafik ist nur in Ethereum-Netzwerken verfügbar." }, - "confirmClear": { - "message": "Möchten Sie die genehmigten Websites wirklich löschen?" - }, "contractInteraction": { "message": "Vertragsinteraktion" }, - "clearApprovalData": { - "message": "Genehmigungsdaten löschen" - }, "reject": { "message": "Ablehnen" }, - "providerRequest": { + "likeToConnect": { "message": "$1 möchte sich mit deinem Account verbinden" }, - "providerRequestInfo": { - "message": "Diese Website fordert Zugriff auf Ihre aktuelle Kontoadresse. Stellen Sie immer sicher, dass Sie den Websites vertrauen, mit denen Sie interagieren." - }, "about": { "message": "Über" }, "aboutSettingsDescription": { - "message": "Version, Supportcenter und Kontaktinformationen." + "message": "Version, Supportcenter und Kontaktinformationen" }, "acceleratingATransaction": { "message": "* Die Beschleunigung einer Transaktion durch die Verwendung eines höheren Gaspreises erhöht die Chancen einer schnelleren Verarbeitung durch das Netz, wofür es allerdings keine Garantie gibt." @@ -60,7 +48,7 @@ "message": "Erweitert" }, "advancedSettingsDescription": { - "message": "Zugriff auf Entwicklerfunktionen, Download von Statusprotokollen, Zurücksetzen des Kontos, Einrichten von Testnetzen und benutzerdefinierten RPCs." + "message": "Zugriff auf Entwicklerfunktionen, Download von Statusprotokollen, Zurücksetzen des Kontos, Einrichten von Testnetzen und benutzerdefinierten RPCs" }, "advancedOptions": { "message": "Erweiterte Optionen" @@ -154,10 +142,6 @@ "basic": { "message": "Grundlegend" }, - "betweenMinAndMax": { - "message": "Muss größer oder gleich $1 und kleiner oder gleich $2 sein.", - "description": "helper for inputting hex as decimal input" - }, "blockExplorerUrl": { "message": "Block-Explorer" }, @@ -240,9 +224,6 @@ "connect": { "message": "Verbinden" }, - "connectRequest": { - "message": "Verbindungsanfrage" - }, "connectingTo": { "message": "Verbindung mit $1 wird hergestellt" }, @@ -282,9 +263,6 @@ "copiedExclamation": { "message": "Kopiert!" }, - "copy": { - "message": "Kopieren" - }, "copyAddress": { "message": "Adresse in die Zwischenablage kopieren" }, @@ -360,9 +338,6 @@ "directDepositEtherExplainer": { "message": "Wenn du bereits Ether besitzt, ist die sofortige Einzahlung die schnellste Methode Ether in deine neue Wallet zu bekommen." }, - "dismiss": { - "message": "Schließen" - }, "done": { "message": "Fertig" }, @@ -528,10 +503,6 @@ "getStarted": { "message": "Erste Schritte" }, - "greaterThanMin": { - "message": "Muss größer oder gleich $1 sein.", - "description": "helper for inputting hex as decimal input" - }, "happyToSeeYou": { "message": "Wir freuen uns, Sie zu sehen." }, @@ -647,10 +618,6 @@ "ledgerAccountRestriction": { "message": "Sie müssen Ihr letztes Konto verwenden, ehe Sie ein neues hinzufügen können." }, - "lessThanMax": { - "message": "Muss kleiner oder gleich $1 sein.", - "description": "helper for inputting hex as decimal input" - }, "letsGoSetUp": { "message": "Ja, legen wir los!" }, @@ -840,9 +807,6 @@ "message": "Füge deine Private Key Zeichenfolge hier ein:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "Füge deine Seed-Wörterfolge hier ein!" - }, "pending": { "message": "ausstehend" }, @@ -1152,9 +1116,6 @@ "storePhrase": { "message": "Speichern Sie diesen Schlüssel in einem Passwortmanager wie 1Password." }, - "submit": { - "message": "Einreichen" - }, "submitted": { "message": "Abgeschickt" }, @@ -1313,9 +1274,6 @@ "userName": { "message": "Nutzername" }, - "validFileImport": { - "message": "Du musst eine gültige Datei für den Import auswählen." - }, "viewAccount": { "message": " Account einsehen" }, diff --git a/app/_locales/el/messages.json b/app/_locales/el/messages.json index 126b9133f..6f35b1447 100644 --- a/app/_locales/el/messages.json +++ b/app/_locales/el/messages.json @@ -1,33 +1,21 @@ { - "privacyModeDefault": { - "message": "Η Λειτουργία Απορρήτου είναι πλέον ενεργοποιημένη από προεπιλογή" - }, "chartOnlyAvailableEth": { "message": "Το διάγραμμα είναι διαθέσιμο μόνο σε δίκτυα Ethereum." }, - "confirmClear": { - "message": "Είστε βέβαιοι ότι θέλετε να διαγράψετε τους εγκεκριμένους ιστότοπους;" - }, "contractInteraction": { "message": "Αλληλεπίδραση Σύμβασης" }, - "clearApprovalData": { - "message": "Εκκαθάριση Δεδομένων Απορρήτου" - }, "reject": { "message": "Απόρριψη" }, - "providerRequest": { + "likeToConnect": { "message": "Αίτημα σύνδεσης στον λογαριασμό σας από $1" }, - "providerRequestInfo": { - "message": "Ο ιστότοπος ζητά πρόσβαση για προβολή της τρέχουσας διεύθυνσης του λογαριασμού σας. Να σιγουρεύεστε πάντα ότι εμπιστεύεστε τους ιστότοπους με τους οποίους αλληλεπιδράτε." - }, "about": { "message": "Σχετικά με" }, "aboutSettingsDescription": { - "message": "Έκδοση, κέντρο υποστήριξης και πληροφορίες επικοινωνίας." + "message": "Έκδοση, κέντρο υποστήριξης και πληροφορίες επικοινωνίας" }, "acceleratingATransaction": { "message": "* Η επιτάχυνση μιας συναλλαγής με τη χρήση υψηλότερης τιμής καυσίμου αυξάνει τις πιθανότητές της για ταχύτερη επεξεργασία από το δίκτυο, αλλά δεν είναι πάντοτε εγγυημένη." @@ -63,7 +51,7 @@ "message": "Σύνθετες" }, "advancedSettingsDescription": { - "message": "Αποκτήστε πρόσβαση στις λειτουργίες του προγραμματιστή, κατεβάστε Αρχεία Καταγραφών Καταστάσεων, Επαναφέρετε τον Λογαριασμό, εγκαταστήστε δοκιμαστικά δίκτυα και προσαρμοσμένα RPC." + "message": "Αποκτήστε πρόσβαση στις λειτουργίες του προγραμματιστή, κατεβάστε Αρχεία Καταγραφών Καταστάσεων, Επαναφέρετε τον Λογαριασμό, εγκαταστήστε δοκιμαστικά δίκτυα και προσαρμοσμένα RPC" }, "advancedOptions": { "message": "Σύνθετες Επιλογές" @@ -157,10 +145,6 @@ "basic": { "message": "Βασικά" }, - "betweenMinAndMax": { - "message": "πρέπει να είναι μεγαλύτερο ή ίσο με $1 και μικρότερο ή ίσο με $2.", - "description": "helper for inputting hex as decimal input" - }, "blockExplorerUrl": { "message": "Εξερευνητής Μπλοκ" }, @@ -249,9 +233,6 @@ "connect": { "message": "Σύνδεση" }, - "connectRequest": { - "message": "Αίτημα Σύνδεσης" - }, "connectingTo": { "message": "Σύνδεση με $1" }, @@ -291,9 +272,6 @@ "copiedExclamation": { "message": "Έγινε αντιγραφή!" }, - "copy": { - "message": "Αντιγραφή" - }, "copyAddress": { "message": "Αντιγράψτε τη διεύθυνση στο πρόχειρο" }, @@ -372,9 +350,6 @@ "directDepositEtherExplainer": { "message": "Αν έχετε ήδη κάποια Ether, ο πιο γρήγορος τρόπος για να πάρετε τα Ether στο νέο σας πορτοφόλι με άμεση κατάθεση." }, - "dismiss": { - "message": "Παράβλεψη" - }, "done": { "message": "Τέλος" }, @@ -540,10 +515,6 @@ "getStarted": { "message": "Έναρξη" }, - "greaterThanMin": { - "message": "πρέπει να είναι μεγαλύτερο ή ίσο με $1.", - "description": "helper for inputting hex as decimal input" - }, "happyToSeeYou": { "message": "Χαιρόμαστε που σας βλέπουμε." }, @@ -662,10 +633,6 @@ "ledgerAccountRestriction": { "message": "Πρέπει να χρησιμοποιήσετε τον τελευταίο σας λογαριασμό πριν προσθέσετε έναν νέο." }, - "lessThanMax": { - "message": "πρέπει να είναι μικρότερο από ή ίσο με $1.", - "description": "helper for inputting hex as decimal input" - }, "letsGoSetUp": { "message": "Ναι, ας το εγκαταστήσουμε!" }, @@ -867,9 +834,6 @@ "message": "Επικολλήστε τη συμβολοσειρά ιδιωτικού κλειδιού εδώ:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "Επικολλήστε τη φράση επαναφοράς σας εδώ!" - }, "pending": { "message": "σε εκκρεμότητα" }, @@ -1183,9 +1147,6 @@ "storePhrase": { "message": "Αποθηκεύστε αυτήν τη φράση σε έναν διαχειριστή κωδικών πρόσβασης όπως το 1Password." }, - "submit": { - "message": "Υποβολή" - }, "submitted": { "message": "Υποβλήθηκε" }, @@ -1350,9 +1311,6 @@ "userName": { "message": "Όνομα χρήστη" }, - "validFileImport": { - "message": "Πρέπει να επιλέξετε έναν έγκυρο φάκελο για εισαγωγή." - }, "viewAccount": { "message": "Προβολή λογαριασμού" }, diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 412d78148..e12f3f4b6 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -5,53 +5,39 @@ "showIncomingTransactionsDescription": { "message": "Select this to use Etherscan to show incoming transactions in the transactions list" }, + "cancelledConnectionWithMetaMask": { + "message": "Cancelled Connection With MetaMask" + }, "chartOnlyAvailableEth": { "message": "Chart only available on Ethereum networks." }, - "confirmClear": { - "message": "Are you sure you want to clear approved websites?" + "connectedSites": { + "message": "Connected Sites" }, - "connections": { - "message": "Connections" + "connectingWithMetaMask": { + "message": "Connecting With MetaMask..." }, - "connectionsSettingsDescription": { - "message": "Sites allowed to read your accounts" + "connectTo": { + "message": "Connect to $1", + "description": "$1 is the name/origin of a site/dapp that the user can connect to metamask" }, - "addSite": { - "message": "Add Site" - }, - "addSiteDescription": { - "message": "Manually add a site to allow it access to your accounts, useful for older dapps" - }, - "connected": { - "message": "Connected" - }, - "connectedDescription": { - "message": "The list of sites allowed access to your addresses" - }, - "privacyModeDefault": { - "message": "Privacy Mode is now enabled by default" + "chooseAnAcount": { + "message": "Choose an account" }, "contractInteraction": { "message": "Contract Interaction" }, - "clearApprovalData": { - "message": "Remove all sites" - }, "reject": { "message": "Reject" }, - "providerRequest": { - "message": "$1 would like to connect to your account" - }, - "providerRequestInfo": { - "message": "This site is requesting access to view your current account address. Always make sure you trust the sites you interact with." + "redirectingBackToDapp": { + "message": "Redirecting back to dapp..." }, "about": { "message": "About" }, "aboutSettingsDescription": { - "message": "Version, support center, and contact info." + "message": "Version, support center, and contact info" }, "acceleratingATransaction": { "message": "* Accelerating a transaction by using a higher gas price increases its chances of getting processed by the network faster, but it is not always guaranteed." @@ -91,7 +77,7 @@ "message": "Advanced" }, "advancedSettingsDescription": { - "message": "Access developer features, download State Logs, Reset Account, setup testnets and custom RPC." + "message": "Access developer features, download State Logs, Reset Account, setup testnets and custom RPC" }, "advancedOptions": { "message": "Advanced Options" @@ -196,10 +182,6 @@ "basic": { "message": "Basic" }, - "betweenMinAndMax": { - "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" }, @@ -300,9 +282,6 @@ "connect": { "message": "Connect" }, - "connectRequest": { - "message": "Connect Request" - }, "connectingTo": { "message": "Connecting to $1" }, @@ -348,9 +327,6 @@ "copiedExclamation": { "message": "Copied!" }, - "copy": { - "message": "Copy" - }, "copyAddress": { "message": "Copy address to clipboard" }, @@ -429,14 +405,36 @@ "details": { "message": "Details" }, + "disconnectAccount": { + "message": "Disconnect account" + }, + "disconnectAll": { + "message": "Disconnect All" + }, + "disconnectAllModalDescription": { + "message": "Are you sure? You will be disconnected from all sites on all accounts." + }, + "disconnectAccountModalDescription": { + "message": "Are you sure? Your account (\"$1\") will be disconnected from this site." + }, + "disconnectAccountQuestion": { + "message": "Disconnect account?" + }, + "disconnectFromThisAccount": { + "message": "Disconnect from this account?" + }, + "disconnectAllAccountsQuestion": { + "message": "Disconnect all accounts?" + }, "directDepositEther": { "message": "Directly Deposit Ether" }, "directDepositEtherExplainer": { "message": "If you already have some Ether, the quickest way to get Ether in your new wallet by direct deposit." }, - "dismiss": { - "message": "Dismiss" + "domainLastConnect": { + "message": "Last Connected: $1", + "description": "$1 is the date at which the user was last connected to a given domain" }, "done": { "message": "Done" @@ -498,6 +496,17 @@ "endOfFlowMessage10": { "message": "All Done" }, + "extensionId": { + "message": "Extension ID: $1", + "description": "$1 is a string of random letters that are the id of another extension connecting to MetaMask" + }, + "externalExtension": { + "message": "External Extension" + }, + "onboardingReturnNotice": { + "message": "\"$1\" will close this tab and direct back to $2", + "description": "Return the user to the site that initiated onboarding" + }, "ensRegistrationError": { "message": "Error in ENS name registration" }, @@ -615,10 +624,6 @@ "getStarted": { "message": "Get Started" }, - "greaterThanMin": { - "message": "must be greater than or equal to $1.", - "description": "helper for inputting hex as decimal input" - }, "happyToSeeYou": { "message": "We’re happy to see you." }, @@ -737,22 +742,28 @@ "max": { "message": "Max" }, + "lastConnected": { + "message": "Last Connected" + }, "learnMore": { "message": "Learn more" }, + "learnAboutRisks": { + "message": "Learn about the risks here." + }, "ledgerAccountRestriction": { "message": "You need to make use your last account before you can add a new one." }, - "lessThanMax": { - "message": "must be less than or equal to $1.", - "description": "helper for inputting hex as decimal input" - }, "letsGoSetUp": { "message": "Yes, let’s get set up!" }, "likeToAddTokens": { "message": "Would you like to add these tokens?" }, + "likeToConnect": { + "message": "$1 would like to connect to your MetaMask account", + "description": "$1 is the name/url of a site/dapp asking to connect to MetaMask" + }, "links": { "message": "Links" }, @@ -884,6 +895,9 @@ "rpcUrl": { "message": "New RPC URL" }, + "onlyConnectTrust": { + "message": "Only connect with sites you trust." + }, "optionalChainId": { "message": "ChainID (optional)" }, @@ -958,9 +972,6 @@ "message": "Paste your private key string here:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "Paste your seed phrase here!" - }, "pending": { "message": "pending" }, @@ -1089,6 +1100,9 @@ "readyToConnect": { "message": "Ready to Connect?" }, + "revokeInPermissions": { + "message": "* You can view and revoke permissions in MetaMask settings." + }, "rinkeby": { "message": "Rinkeby Test Network" }, @@ -1351,6 +1365,14 @@ "testFaucet": { "message": "Test Faucet" }, + "thisWillAllow": { + "message": "This will allow $1 to:", + "description": "$1 is the name or domain of a site/dapp that is requesting permissions" + }, + "thisWillAllowExternalExtension": { + "message": "This will allow an external extension with id $1 to:", + "description": "$1 is a string of random letters that are the id of another extension connecting to MetaMask" + }, "thisWillCreate": { "message": "This will create a new wallet and seed phrase" }, @@ -1363,6 +1385,10 @@ "toWithColon": { "message": "To:" }, + "toConnectWith": { + "message": "To connect with $1", + "description": "$1 is the name or domain of a site/dapp that asking to connect with MetaMask" + }, "toETHviaShapeShift": { "message": "$1 to ETH via ShapeShift", "description": "system will fill in deposit type in start of message" @@ -1492,9 +1518,6 @@ "userName": { "message": "Username" }, - "validFileImport": { - "message": "You must select a valid file to import." - }, "viewAccount": { "message": "View Account" }, diff --git a/app/_locales/es/messages.json b/app/_locales/es/messages.json index e503cd977..f64105f26 100644 --- a/app/_locales/es/messages.json +++ b/app/_locales/es/messages.json @@ -1,28 +1,16 @@ { - "privacyModeDefault": { - "message": "Modo Privado está activado ahora por defecto" - }, "chartOnlyAvailableEth": { "message": "Tabla solo disponible en redes Ethereum." }, - "confirmClear": { - "message": "¿Seguro que quieres borrar los sitios web aprobados?" - }, "contractInteraction": { "message": "Interacción con contrato" }, - "clearApprovalData": { - "message": "Borrar datos de aprobación" - }, "reject": { "message": "Rechazar" }, - "providerRequest": { + "likeToConnect": { "message": "$1 quisiera conectar con tu cuenta" }, - "providerRequestInfo": { - "message": "El dominio que se muestra a continuación intenta solicitar acceso a la API Ethereum para que pueda interactuar con la blockchain de Ethereum. Siempre verifique que esté en el sitio correcto antes de aprobar el acceso Ethereum." - }, "about": { "message": "Acerca" }, @@ -145,10 +133,6 @@ "basic": { "message": "Básico" }, - "betweenMinAndMax": { - "message": "Debe ser mayor o igual a $1 y menor o igual a $2", - "description": "helper for inputting hex as decimal input" - }, "blockiesIdenticon": { "message": "Usar Blockies Identicon (Iconos)" }, @@ -209,9 +193,6 @@ "connect": { "message": "Conectar" }, - "connectRequest": { - "message": "Petición para conectar" - }, "connectingTo": { "message": "Conectánodse a $1" }, @@ -251,9 +232,6 @@ "copiedExclamation": { "message": "¡Copiado!" }, - "copy": { - "message": "Copiar" - }, "copyAddress": { "message": "Copiar la dirección al portapapeles" }, @@ -332,9 +310,6 @@ "directDepositEtherExplainer": { "message": "Si posees Ether, la forma más rápida de transferirlo a tu nueva billetera es depositándolo directamente" }, - "dismiss": { - "message": "Descartar" - }, "done": { "message": "Completo" }, @@ -449,10 +424,6 @@ "getHelp": { "message": "Pedir ayuda." }, - "greaterThanMin": { - "message": "Debe ser mayor o igual a $1", - "description": "helper for inputting hex as decimal input" - }, "hardwareWalletConnected": { "message": "Se ha conectado el monedero físico" }, @@ -553,10 +524,6 @@ "ledgerAccountRestriction": { "message": "Hay que hacer uso de tu última cuenta antes de agregarle una nueva." }, - "lessThanMax": { - "message": "Debe ser menor o igual a $1", - "description": "helper for inputting hex as decimal input" - }, "likeToAddTokens": { "message": "¿Te gustaría agregar estos tokens?" }, @@ -701,9 +668,6 @@ "message": "Pega tu clave privada aqui", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "¡Pega tu frase semilla aquí!" - }, "pending": { "message": "pendiente" }, @@ -966,9 +930,6 @@ "step3HardwareWalletMsg": { "message": "Usa tu cuenta física igual que harías con cualquier cuenta de Ethereum. Regístrate con dApps, manda Eth, compra y almacena tokens de ERC20 y otros tokens no-fungibles, como CryptoKitties." }, - "submit": { - "message": "Enviar" - }, "submitted": { "message": "Enviado" }, @@ -1097,9 +1058,6 @@ "usedByClients": { "message": "Utilizado por una variedad de clientes diferentes" }, - "validFileImport": { - "message": "Debes selecionar un archivo valido para importar" - }, "viewAccount": { "message": "Mirar cuenta" }, diff --git a/app/_locales/es_419/messages.json b/app/_locales/es_419/messages.json index c861be81b..0645b0eaf 100644 --- a/app/_locales/es_419/messages.json +++ b/app/_locales/es_419/messages.json @@ -1,33 +1,21 @@ { - "privacyModeDefault": { - "message": "El modo de privacidad está habilitado de manera predeterminada" - }, "chartOnlyAvailableEth": { "message": "Chart está disponible únicamente en las redes de Ethereum." }, - "confirmClear": { - "message": "¿Estás seguro de que deseas borrar los sitios web aprobados?" - }, "contractInteraction": { "message": "Interacción contractual" }, - "clearApprovalData": { - "message": "Borrar datos de privacidad" - }, "reject": { "message": "Rechazar" }, - "providerRequest": { + "likeToConnect": { "message": "$1 desea conectarse a tu cuenta" }, - "providerRequestInfo": { - "message": "Este sitio está solicitando acceso para ver la dirección de tu cuenta corriente. Asegúrate siempre de que confías en los sitios con los que interactúas." - }, "about": { "message": "Acerca de" }, "aboutSettingsDescription": { - "message": "Versión, centro de soporte técnico e información de contacto." + "message": "Versión, centro de soporte técnico e información de contacto" }, "acceleratingATransaction": { "message": "* Aumentar una transacción utilizando un precio de gas más alto aumenta sus posibilidades de ser procesada más rápido por la red, pero esto no siempre está garantizado." @@ -63,7 +51,7 @@ "message": "Avanzada" }, "advancedSettingsDescription": { - "message": "Accede a las funciones de desarrollador, descarga los registros de estado, restablece la cuenta, y configura las redes Testnet y el RPC personalizado." + "message": "Accede a las funciones de desarrollador, descarga los registros de estado, restablece la cuenta, y configura las redes Testnet y el RPC personalizado" }, "advancedOptions": { "message": "Opciones avanzadas" @@ -157,10 +145,6 @@ "basic": { "message": "Básicas" }, - "betweenMinAndMax": { - "message": "debe ser superior o igual a $1 e inferior o igual a $2.", - "description": "helper for inputting hex as decimal input" - }, "blockExplorerUrl": { "message": "Bloquear Explorer" }, @@ -249,9 +233,6 @@ "connect": { "message": "Conectar" }, - "connectRequest": { - "message": "Solicitud de conexión" - }, "connectingTo": { "message": "Conexión con $1" }, @@ -291,9 +272,6 @@ "copiedExclamation": { "message": "Copiado" }, - "copy": { - "message": "Copiar" - }, "copyAddress": { "message": "Copiar la dirección al portapapeles" }, @@ -372,9 +350,6 @@ "directDepositEtherExplainer": { "message": "Si ya tienes algunos Ethers, la forma más rápida de ingresarlos en tu nueva billetera es a través de un depósito directo." }, - "dismiss": { - "message": "Rechazar" - }, "done": { "message": "Listo" }, @@ -537,10 +512,6 @@ "getStarted": { "message": "Comenzar" }, - "greaterThanMin": { - "message": "debe ser superior o igual a $1.", - "description": "helper for inputting hex as decimal input" - }, "happyToSeeYou": { "message": "Estamos encantados de verte." }, @@ -656,10 +627,6 @@ "ledgerAccountRestriction": { "message": "Necesitas utilizar tu cuenta anterior para agregar una nueva." }, - "lessThanMax": { - "message": "debe ser menor o igual a $1.", - "description": "helper for inputting hex as decimal input" - }, "letsGoSetUp": { "message": "¡Eso, pongámonos en marcha!" }, @@ -855,9 +822,6 @@ "message": "Copia y pega tu cadena de claves privada aquí:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "¡Copia y pega tu frase de inicialización aquí!" - }, "pending": { "message": "pendiente" }, @@ -1171,9 +1135,6 @@ "storePhrase": { "message": "Almacena esta frase en un administrador de contraseñas como 1Password." }, - "submit": { - "message": "Enviar" - }, "submitted": { "message": "Enviado" }, @@ -1335,9 +1296,6 @@ "userName": { "message": "Nombre de usuario" }, - "validFileImport": { - "message": "Selecciona un archivo válido para importar." - }, "viewAccount": { "message": "Ver cuenta" }, diff --git a/app/_locales/et/messages.json b/app/_locales/et/messages.json index c779dff85..ad64a32cb 100644 --- a/app/_locales/et/messages.json +++ b/app/_locales/et/messages.json @@ -1,33 +1,21 @@ { - "privacyModeDefault": { - "message": "Privaatsusrežiim on nüüd vaikimisi lubatud" - }, "chartOnlyAvailableEth": { "message": "Tabel on saadaval vaid Ethereumi võrkudes." }, - "confirmClear": { - "message": "Kas soovite kindlasti kinnitatud veebisaidid kustutada?" - }, "contractInteraction": { "message": "Lepingu suhtlus" }, - "clearApprovalData": { - "message": "Tühjenda privaatsusandmed" - }, "reject": { "message": "Lükka tagasi" }, - "providerRequest": { + "likeToConnect": { "message": "$1 soovib teie kontoga ühenduse luua" }, - "providerRequestInfo": { - "message": "See sait taotleb juurdepääsu teie praeguse konto aadressi vaatamiseks. Veenduge alati, et usaldate saite, millega suhtlete." - }, "about": { "message": "Teave" }, "aboutSettingsDescription": { - "message": "Versioon, tugikeskus ja kontaktteave." + "message": "Versioon, tugikeskus ja kontaktteave" }, "acceleratingATransaction": { "message": "* Tehingu kiirendamine kõrgemate gaasihindadega suurendab võimalust kiiremaks võrgus töötlemiseks, kuid see ei ole alati tagatud." @@ -63,7 +51,7 @@ "message": "Täpsemad" }, "advancedSettingsDescription": { - "message": "Juurdepääs arendaja funktsioonidele, olekulogide allalaadimine, konto lähtestamine, testvõrkude ja kohandatud RPC-de seadistamine." + "message": "Juurdepääs arendaja funktsioonidele, olekulogide allalaadimine, konto lähtestamine, testvõrkude ja kohandatud RPC-de seadistamine" }, "advancedOptions": { "message": "Täpsemad suvandid" @@ -157,10 +145,6 @@ "basic": { "message": "Põhiseaded" }, - "betweenMinAndMax": { - "message": "peab olema suurem või võrdne $1 ja väiksem või võrdne $2.", - "description": "helper for inputting hex as decimal input" - }, "blockExplorerUrl": { "message": "Blokeeri Explorer" }, @@ -252,9 +236,6 @@ "connect": { "message": "Ühendamine" }, - "connectRequest": { - "message": "Ühenduse taotlus" - }, "connectingTo": { "message": "Ühenduse loomine $1" }, @@ -294,9 +275,6 @@ "copiedExclamation": { "message": "Kopeeritud!" }, - "copy": { - "message": "Kopeeri" - }, "copyAddress": { "message": "Kopeeri aadress lõikelauale" }, @@ -375,9 +353,6 @@ "directDepositEtherExplainer": { "message": "Kui teil on juba veidi eetrit, on kiirem viis eetri rahakotti saamiseks otsene sissemakse." }, - "dismiss": { - "message": "Loobu" - }, "done": { "message": "Valmis" }, @@ -427,7 +402,7 @@ "message": "Kui teil on küsimusi või näete midagi kahtlast, kirjutage meile support@metamask.io." }, "endOfFlowMessage8": { - "message": "Metamask ei saa teie seemnefraasi taastada. Lisateave." + "message": "MetaMask ei saa teie seemnefraasi taastada. Lisateave." }, "endOfFlowMessage9": { "message": "Lisateave." @@ -539,10 +514,6 @@ "getStarted": { "message": "Alustamine" }, - "greaterThanMin": { - "message": "peab olema $1või suurem.", - "description": "helper for inputting hex as decimal input" - }, "happyToSeeYou": { "message": "Meil on hea meel teid näha." }, @@ -658,10 +629,6 @@ "ledgerAccountRestriction": { "message": "Enne uue konto loomist peate kasutama eelmist kontot." }, - "lessThanMax": { - "message": "peab olema $1või väiksem.", - "description": "helper for inputting hex as decimal input" - }, "letsGoSetUp": { "message": "Jah, hakkame pihta!" }, @@ -860,9 +827,6 @@ "message": "Kleepige oma privaatne võtmestring siia:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "Kleepige oma seemnefraas siia!" - }, "pending": { "message": "ootel" }, @@ -1176,9 +1140,6 @@ "storePhrase": { "message": "Salvestage see fraas paroolihaldurisse, nagu 1Password." }, - "submit": { - "message": "Esita" - }, "submitted": { "message": "Edastatud" }, @@ -1201,7 +1162,7 @@ "message": "Saate sünkroonida oma kontod ja teabe oma mobiiliseadmega. Avage MetaMaski mobiilirakendus, avage \"Settings\" (Seaded) ja puudutage valikut \"Sync from Browser Extension\" (Sünkroonimine lehitseja laiendusest)" }, "syncWithMobileDescNewUsers": { - "message": "Järgige Metamaski mobiilirakenduse esmakordsel avamisel telefonis esitatud samme." + "message": "Järgige MetaMaski mobiilirakenduse esmakordsel avamisel telefonis esitatud samme." }, "syncWithMobileScanThisCode": { "message": "Skanneerige see kood MetaMaski mobiilirakendusega" @@ -1346,9 +1307,6 @@ "userName": { "message": "Kasutajanimi" }, - "validFileImport": { - "message": "Peate valima importimiseks sobiva faili." - }, "viewAccount": { "message": "Kuva konto" }, diff --git a/app/_locales/fa/messages.json b/app/_locales/fa/messages.json index 5e00dde11..5be054e7c 100644 --- a/app/_locales/fa/messages.json +++ b/app/_locales/fa/messages.json @@ -1,28 +1,16 @@ { - "privacyModeDefault": { - "message": "وضعیت محرمیت حالا بصورت خودکار فعال است" - }, "chartOnlyAvailableEth": { "message": "تنها قابل دسترس را در شبکه های ایتریوم جدول بندی نمایید" }, - "confirmClear": { - "message": "آیا مطمئن هستید تا وب سایت های تصدیق شده را حذف کنید؟" - }, "contractInteraction": { "message": "تعامل قرارداد" }, - "clearApprovalData": { - "message": "حذف اطلاعات حریم خصوصی" - }, "reject": { "message": "عدم پذیرش" }, - "providerRequest": { + "likeToConnect": { "message": "1$1 میخواهید تا با حساب تان وصل شوید" }, - "providerRequestInfo": { - "message": "این سایت در حال درخواست دسترسی است تا آدرس فعلی حساب تان را مشاهده نماید. همیشه متوجه باشید که بالای سایتی که با آن معامله میکنید، اعتماد دارید یا خیر." - }, "about": { "message": "درباره" }, @@ -157,10 +145,6 @@ "basic": { "message": "پایه" }, - "betweenMinAndMax": { - "message": "باید بزرگتر از یا مساوی به 1$1 و کوچکتر یا مساوی به 2$2 باشد.", - "description": "helper for inputting hex as decimal input" - }, "blockExplorerUrl": { "message": "بلاک کردن براوزر" }, @@ -252,9 +236,6 @@ "connect": { "message": "اتصال" }, - "connectRequest": { - "message": "درخواست اتصال" - }, "connectingTo": { "message": "در حال اتصال به 1$1" }, @@ -294,9 +275,6 @@ "copiedExclamation": { "message": "کپی شد!" }, - "copy": { - "message": "کپی" - }, "copyAddress": { "message": "کاپی آدرس به کلیپ بورد" }, @@ -375,9 +353,6 @@ "directDepositEtherExplainer": { "message": "در صورتیکه شما کدام ایتر داشته باشید، سریعترین روش برای گرفتن ایتر در کیف جدید تان توسط پرداخت مستقیم." }, - "dismiss": { - "message": "لغو کردن" - }, "done": { "message": "تمام" }, @@ -543,10 +518,6 @@ "getStarted": { "message": "شروع به کار" }, - "greaterThanMin": { - "message": "باید بزرگتر از یا مساوی به 1$1 باشد.", - "description": "helper for inputting hex as decimal input" - }, "happyToSeeYou": { "message": "از دیدن شما خوشحال هستیم." }, @@ -665,10 +636,6 @@ "ledgerAccountRestriction": { "message": "لازم است تا حساب قبلی تان را قبل از اینکه جدید اضافه کنید، مورد استفاده قرار دهید." }, - "lessThanMax": { - "message": "باید کوچکتر از 1$1 یا مساوی با آن باشد.", - "description": "helper for inputting hex as decimal input" - }, "letsGoSetUp": { "message": "بلی، درست شد!" }, @@ -870,9 +837,6 @@ "message": "زنجیره کلید شخصی را در اینجا کاپی کنید:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "عبارت بازیاب تان را اینجا بگذارید!" - }, "pending": { "message": "در حال تعلیق" }, @@ -1186,9 +1150,6 @@ "storePhrase": { "message": "این عبارت را در یک نرم افزار مدیریت رمز عبور مانند 1Password ذخیره نمایید." }, - "submit": { - "message": "ارائه" - }, "submitted": { "message": "ارائه شد" }, @@ -1356,9 +1317,6 @@ "userName": { "message": "نام کاربری" }, - "validFileImport": { - "message": "شما باید یک فایل معتبر را جهت وارد سازی انتخاب کنید." - }, "viewAccount": { "message": "مشاهده حساب" }, diff --git a/app/_locales/fi/messages.json b/app/_locales/fi/messages.json index df4f62a19..56f48a6fd 100644 --- a/app/_locales/fi/messages.json +++ b/app/_locales/fi/messages.json @@ -1,33 +1,21 @@ { - "privacyModeDefault": { - "message": "Yksityisyystila on nyt oletusarvoisesti käytössä" - }, "chartOnlyAvailableEth": { "message": "Kaavio saatavilla vain Ethereum-verkoissa." }, - "confirmClear": { - "message": "Haluatko varmasti tyhjentää hyväksytyt verkkosivustot?" - }, "contractInteraction": { "message": "Sopimustoiminta" }, - "clearApprovalData": { - "message": "Tyhjennä yksityisyystiedot" - }, "reject": { "message": "Hylkää" }, - "providerRequest": { + "likeToConnect": { "message": "$1 haluaisi yhdistää tiliisi" }, - "providerRequestInfo": { - "message": "Tämä sivusto pyytää oikeuksia nähdä nykyisen tiliosoitteesi. Varmista aina, että luotat sivustoihin, joiden kanssa toimit." - }, "about": { "message": "Tietoja asetuksista" }, "aboutSettingsDescription": { - "message": "Versio, tukikeskus ja yhteystiedot." + "message": "Versio, tukikeskus ja yhteystiedot" }, "acceleratingATransaction": { "message": "* Tapahtuman nopeuttaminen käyttämällä korkeampaa gas-hintaa parantaa mahdollisuutta, että verkko käsittelee sen nopeammin, mutta tämä ei ole aina taattua." @@ -63,7 +51,7 @@ "message": "Lisäasetukset" }, "advancedSettingsDescription": { - "message": "Käytä kehittäjän ominaisuuksia, lataa tilalokeja, palauta tilit, asenna testiverkostoja ja muokattavia RPC:itä." + "message": "Käytä kehittäjän ominaisuuksia, lataa tilalokeja, palauta tilit, asenna testiverkostoja ja muokattavia RPC:itä" }, "advancedOptions": { "message": "Tarkemmat vaihtoehdot" @@ -157,10 +145,6 @@ "basic": { "message": "Perusvaihtoehdot" }, - "betweenMinAndMax": { - "message": "tulee olla vähintään $1 ja korkeintaan $2.", - "description": "helper for inputting hex as decimal input" - }, "blockExplorerUrl": { "message": "Lohkonhallinta" }, @@ -249,9 +233,6 @@ "connect": { "message": "Muodosta yhteys" }, - "connectRequest": { - "message": "Yhdistämispyyntö" - }, "connectingTo": { "message": "Yhdistetään summaan $1 " }, @@ -291,9 +272,6 @@ "copiedExclamation": { "message": "Kopioitu" }, - "copy": { - "message": "Kopioi" - }, "copyAddress": { "message": "Kopioi osoite leikepöydälle" }, @@ -372,9 +350,6 @@ "directDepositEtherExplainer": { "message": "Jos sinulla on jo etheriä, nopein tapa hankkia etheriä uuteen lompakkoosi on suoratalletus." }, - "dismiss": { - "message": "Piilota" - }, "done": { "message": "Valmis" }, @@ -540,10 +515,6 @@ "getStarted": { "message": "Aloitusopas" }, - "greaterThanMin": { - "message": "on oltava vähintään $1.", - "description": "helper for inputting hex as decimal input" - }, "happyToSeeYou": { "message": "Meistä on mukava nähdä sinut." }, @@ -662,10 +633,6 @@ "ledgerAccountRestriction": { "message": "Sinun tarvitsee käyttää edellistä tiliäsi ennen kuin voit lisätä uuden." }, - "lessThanMax": { - "message": "tulee olla korkeintaan $1.", - "description": "helper for inputting hex as decimal input" - }, "letsGoSetUp": { "message": "Kyllä, valmistaudutaan!" }, @@ -867,9 +834,6 @@ "message": "Liitä yksityisen avaimesi merkkijono tähän:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "Liitä salaustekstisi tähän!" - }, "pending": { "message": "odottaa" }, @@ -1183,9 +1147,6 @@ "storePhrase": { "message": "Tallenna tämä teksti johonkin salasanojen hallintaohjelmaan (esim. 1Password)." }, - "submit": { - "message": "Lähetä" - }, "submitted": { "message": "Lähetetty" }, @@ -1353,9 +1314,6 @@ "userName": { "message": "Käyttäjätunnus" }, - "validFileImport": { - "message": "Valittava tuotavaksi kelpaava tiedosto." - }, "viewAccount": { "message": "Näytä tili" }, diff --git a/app/_locales/fil/messages.json b/app/_locales/fil/messages.json index d00726811..cd9b31234 100644 --- a/app/_locales/fil/messages.json +++ b/app/_locales/fil/messages.json @@ -1,33 +1,21 @@ { - "privacyModeDefault": { - "message": "Naka-enable ang Privacy Mode bilang default" - }, "chartOnlyAvailableEth": { "message": "Available lang ang chart sa mga Ethereum network." }, - "confirmClear": { - "message": "Sigurado ka bang gusto mong i-clear ang mga inaprubahang website?" - }, "contractInteraction": { "message": "Paggamit sa Contract" }, - "clearApprovalData": { - "message": "I-clear ang Privacy Data" - }, "reject": { "message": "Tanggihan" }, - "providerRequest": { + "likeToConnect": { "message": "Gusto ng $1 na kumonekta sa iyong account" }, - "providerRequestInfo": { - "message": "Humihiling ng access ang site na ito na tingnan ang kasalukuyan mong account address. Palaging tiyaking pinagkakatiwalaan mo ang mga site na pinupuntahan mo." - }, "about": { "message": "Tungkol sa" }, "aboutSettingsDescription": { - "message": "Bersyon, support center, at impormasyon sa pakikipag-ugnayan." + "message": "Bersyon, support center, at impormasyon sa pakikipag-ugnayan" }, "acceleratingATransaction": { "message": "* Ang pagpapabilis sa isang transaksyon sa pamamagitan ng paggamit ng mas mataas na presyo ng gas ay makakadagdag sa tsansa nitong maproseso ng network nang mas mabilis, pero hindi ito palaging garantisado." @@ -57,7 +45,7 @@ "message": "Magdagdag ng Recipient" }, "advancedSettingsDescription": { - "message": "I-access ang mga feature para sa mga developer, mag-download ng mga State Log, I-reset ang Account, mag-set up ng mga testnet at custom RPC." + "message": "I-access ang mga feature para sa mga developer, mag-download ng mga State Log, I-reset ang Account, mag-set up ng mga testnet at custom RPC" }, "advancedOptions": { "message": "Mga Advanced na Opsyon" @@ -145,10 +133,6 @@ "basic": { "message": "Pangunahin" }, - "betweenMinAndMax": { - "message": "dapat ay mas malaki sa o katumbas ng $1 at mas maliit sa o katumbas ng $2.", - "description": "helper for inputting hex as decimal input" - }, "blockExplorerView": { "message": "Tingnan ang account sa $1", "description": "$1 replaced by URL for custom block explorer" @@ -342,9 +326,6 @@ "directDepositEtherExplainer": { "message": "Kung mayroon ka nang Ether, ang pinakamabilis na paraan para magkaroon ng Ether sa iyong bagong wallet ay sa pamamagitan ng direkang deposito." }, - "dismiss": { - "message": "Balewalain" - }, "done": { "message": "Tapos na" }, @@ -503,10 +484,6 @@ "getStarted": { "message": "Magsimula" }, - "greaterThanMin": { - "message": "dapat ay mas malaki sa o katumbas ng $1.", - "description": "helper for inputting hex as decimal input" - }, "happyToSeeYou": { "message": "Masaya kaming makita ka." }, @@ -606,10 +583,6 @@ "ledgerAccountRestriction": { "message": "Kailangan mong gamitin ang iyong dating account bago ka makapagdagdag ng bago." }, - "lessThanMax": { - "message": "dapat ay mas mababa sa o katumbas ng $1.", - "description": "helper for inputting hex as decimal input" - }, "letsGoSetUp": { "message": "Oo, i-set up natin ito!" }, @@ -792,9 +765,6 @@ "message": "I-paste ang iyong private key string dito:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "I-paste ang iyong seed phrase dito!" - }, "pending": { "message": "nakabinbin" }, @@ -1083,9 +1053,6 @@ "storePhrase": { "message": "I-store ang pariralang ito sa isang password manager tulad ng 1Password." }, - "submit": { - "message": "Isumite" - }, "submitted": { "message": "Isinumite" }, @@ -1241,9 +1208,6 @@ "usedByClients": { "message": "Ginagamit ng iba't ibang client" }, - "validFileImport": { - "message": "Dapat kang pumili ng valid na file na ii-import." - }, "viewAccount": { "message": "Tingnan ang Account" }, diff --git a/app/_locales/fr/messages.json b/app/_locales/fr/messages.json index 98ae1576b..1fc2a600d 100644 --- a/app/_locales/fr/messages.json +++ b/app/_locales/fr/messages.json @@ -1,33 +1,21 @@ { - "privacyModeDefault": { - "message": "Le mode conversation privée est maintenant activé par défaut" - }, "chartOnlyAvailableEth": { "message": "Tableau disponible uniquement sur les réseaux Ethereum." }, - "confirmClear": { - "message": "Êtes-vous sûr de vouloir supprimer les sites Web approuvés?" - }, "contractInteraction": { "message": "Interaction avec un contrat" }, - "clearApprovalData": { - "message": "Effacer les données d'approbation" - }, "reject": { "message": "Rejeter" }, - "providerRequest": { + "likeToConnect": { "message": "$1 voudrait se connecter à votre compte" }, - "providerRequestInfo": { - "message": "Le domaine répertorié ci-dessous tente de demander l'accès à l'API Ethereum pour pouvoir interagir avec la chaîne de blocs Ethereum. Vérifiez toujours que vous êtes sur le bon site avant d'autoriser l'accès à Ethereum." - }, "about": { "message": "À propos" }, "aboutSettingsDescription": { - "message": "Version, centre d'assistance et coordonnées." + "message": "Version, centre d'assistance et coordonnées" }, "acceleratingATransaction": { "message": "* Accélérer une transaction en utilisant un prix de l'essence plus élevé augmente ses chances d'être traitée plus rapidement par le réseau, mais ce n'est pas toujours garanti." @@ -63,7 +51,7 @@ "message": "Paramètres avancés" }, "advancedSettingsDescription": { - "message": "Accédez aux fonctionnalités pour les développeurs, téléchargez State Logs, réinitialisez votre compte, configurez testnets et personnalisez RPC." + "message": "Accédez aux fonctionnalités pour les développeurs, téléchargez State Logs, réinitialisez votre compte, configurez testnets et personnalisez RPC" }, "advancedOptions": { "message": "Options avancées" @@ -154,10 +142,6 @@ "basic": { "message": "Général" }, - "betweenMinAndMax": { - "message": "doit être supérieur ou égal à $1 et inférieur ou égal à $2", - "description": "helper for inputting hex as decimal input" - }, "blockExplorerView": { "message": "Afficher le compte à $1", "description": "$1 replaced by URL for custom block explorer" @@ -240,9 +224,6 @@ "connect": { "message": "Connecter" }, - "connectRequest": { - "message": "Demande de connexion" - }, "connectingTo": { "message": "Connexion à $1" }, @@ -282,9 +263,6 @@ "copiedExclamation": { "message": "Copié!" }, - "copy": { - "message": "Copier" - }, "copyAddress": { "message": "Copier l'addresse dans le presse-papier" }, @@ -363,9 +341,6 @@ "directDepositEtherExplainer": { "message": "Si vous avez déjà de l'Ether, le moyen le plus rapide d'obtenir des Ether dans votre nouveau portefeuille est par dépôt direct." }, - "dismiss": { - "message": "Ignorer" - }, "done": { "message": "Terminé" }, @@ -531,10 +506,6 @@ "getStarted": { "message": "Démarrer" }, - "greaterThanMin": { - "message": "doit être supérieur ou égal à $1.", - "description": "helper for inputting hex as decimal input" - }, "happyToSeeYou": { "message": "Nous sommes ravis de vous voir." }, @@ -641,10 +612,6 @@ "ledgerAccountRestriction": { "message": "Vous devez d'abord utiliser le dernier compte que vous avez créé avant de pouvoir en créer un autre." }, - "lessThanMax": { - "message": "doit être inférieur ou égal à $1.", - "description": "helper for inputting hex as decimal input" - }, "letsGoSetUp": { "message": "Oui, passons à la configuration !" }, @@ -837,9 +804,6 @@ "message": "Collez votre clé privée ici:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "Collez votre seed phrase ici!" - }, "pending": { "message": "En attente" }, @@ -1153,9 +1117,6 @@ "storePhrase": { "message": "Stockez cette phrase dans un gestionnaire de mots de passe comme 1Password." }, - "submit": { - "message": "Soumettre" - }, "submitted": { "message": "Envoyé" }, @@ -1317,9 +1278,6 @@ "userName": { "message": "Nom d'utilisateur" }, - "validFileImport": { - "message": "Vous devez selectionner un fichier valide à importer." - }, "viewAccount": { "message": "Afficher le compte" }, diff --git a/app/_locales/gu/messages.json b/app/_locales/gu/messages.json index bcd467be1..d04514fd9 100644 --- a/app/_locales/gu/messages.json +++ b/app/_locales/gu/messages.json @@ -42,9 +42,6 @@ "connect": { "message": "કનેક્ટ કરો" }, - "copy": { - "message": "કૉપિ કરો" - }, "copyToClipboard": { "message": "ક્લિપબોર્ડ પર કૉપિ કરો" }, @@ -57,9 +54,6 @@ "details": { "message": "વિગતો" }, - "dismiss": { - "message": "કાઢી નાખો" - }, "done": { "message": "થઈ ગયું" }, @@ -131,9 +125,6 @@ "settings": { "message": "સેટિંગ્સ" }, - "submit": { - "message": "સબમિટ કરો" - }, "tryAgain": { "message": "ફરી પ્રયાસ કરો" }, diff --git a/app/_locales/he/messages.json b/app/_locales/he/messages.json index 41813baf2..d0f92848c 100644 --- a/app/_locales/he/messages.json +++ b/app/_locales/he/messages.json @@ -1,28 +1,16 @@ { - "privacyModeDefault": { - "message": "מצב פרטיות זמין עכשיו כברירת מחדל" - }, "chartOnlyAvailableEth": { "message": "טבלה זמינה רק ברשתות אתריום." }, - "confirmClear": { - "message": "הנך בטוח/ה כי ברצונך למחוק אתרים שאושרו?" - }, "contractInteraction": { "message": "אינטראקציית חוזה" }, - "clearApprovalData": { - "message": "נקה נתוני פרטיות" - }, "reject": { "message": "דחה" }, - "providerRequest": { + "likeToConnect": { "message": "$1 מבקש להתחבר לחשבון שלך" }, - "providerRequestInfo": { - "message": "אתר זה מבקש גישה לצפייה בכתובת החשבון הנוכחית שלך. יש לוודא תמיד כי הנך בוטח/ת באתרים עמם הנך מתקשר/ת." - }, "about": { "message": "מידע כללי" }, @@ -157,10 +145,6 @@ "basic": { "message": "בסיסי" }, - "betweenMinAndMax": { - "message": "חייב להיות גדול או שווה ל-$1 ופחות או שווה ל-$2.", - "description": "helper for inputting hex as decimal input" - }, "blockExplorerUrl": { "message": "סייר בלוקים" }, @@ -252,9 +236,6 @@ "connect": { "message": "התחברות" }, - "connectRequest": { - "message": "חבר/י בקשה" - }, "connectingTo": { "message": "מתחבר ל- $1 " }, @@ -294,9 +275,6 @@ "copiedExclamation": { "message": "הועתק!" }, - "copy": { - "message": "העתק" - }, "copyAddress": { "message": "העתק כתובת ללוח" }, @@ -375,9 +353,6 @@ "directDepositEtherExplainer": { "message": "אם כבר יש ברשותך את'ר (Ether) , הדרך המהירה ביותר להכניס את'ר לארנק החדש שלך היא באמצעות הפקדה ישירה." }, - "dismiss": { - "message": "סגור" - }, "done": { "message": "סיום" }, @@ -543,10 +518,6 @@ "getStarted": { "message": "תחילת העבודה" }, - "greaterThanMin": { - "message": "חייב להיות גדול או שווה ל-$1.", - "description": "helper for inputting hex as decimal input" - }, "happyToSeeYou": { "message": "אנו שמחים לראותך." }, @@ -665,10 +636,6 @@ "ledgerAccountRestriction": { "message": "עליך להשתמש בחשבון האחרון שלך לפני שתוכל/י להוסיף חשבון חדש." }, - "lessThanMax": { - "message": "חייב להיות פחות או שווה ל- $1.", - "description": "helper for inputting hex as decimal input" - }, "letsGoSetUp": { "message": "כן, בוא נתקין!" }, @@ -867,9 +834,6 @@ "message": "הדבק/י את מחרוזת המפתח הפרטי שלך כאן:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "הדבק/י את ה-seed phrase של כאן!" - }, "pending": { "message": "בהמתנה" }, @@ -1180,9 +1144,6 @@ "storePhrase": { "message": "אחסנ/י צירוף זה במנהל ססמאות כמו 1Password." }, - "submit": { - "message": "שלח" - }, "submitted": { "message": "הוגש" }, @@ -1350,9 +1311,6 @@ "userName": { "message": "שם משתמש" }, - "validFileImport": { - "message": "עליך לבחור קובץ חוקי לייבוא." - }, "viewAccount": { "message": "הצג חשבון" }, diff --git a/app/_locales/hi/messages.json b/app/_locales/hi/messages.json index 19834411e..51aa21b84 100644 --- a/app/_locales/hi/messages.json +++ b/app/_locales/hi/messages.json @@ -1,28 +1,16 @@ { - "privacyModeDefault": { - "message": "गोपनीय मोड अब डिफ़ॉल्ट रूप से सक्षम है" - }, "chartOnlyAvailableEth": { "message": "केवल ईथरअम नेटवर्क पर उपलब्ध चार्ट।" }, - "confirmClear": { - "message": "क्या आप वाकई स्वीकृत वेबसाइटों को क्लियर करना चाहते हैं?" - }, "contractInteraction": { "message": "कॉन्ट्रैक्ट की बातचीत" }, - "clearApprovalData": { - "message": "गोपनीयता डेटा रिक्त करें" - }, "reject": { "message": "अस्‍वीकार करें" }, - "providerRequest": { + "likeToConnect": { "message": "$1 आपके खाते से कनेक्ट होता चाहता हैं" }, - "providerRequestInfo": { - "message": "यह साइट आपके वर्तमान खाते का पता देखने के लिए एक्सेस का अनुरोध कर रही है। हमेशा सुनिश्चित करें कि आप जिन साइटों पर जाते हैं वे विश्वसनीय हैं।" - }, "about": { "message": "इसके बारे में" }, @@ -157,10 +145,6 @@ "basic": { "message": "मूलभूत" }, - "betweenMinAndMax": { - "message": "$1 से अधिक या बराबर होना चाहिए और $2 के बराबर या उससे कम होना चाहिए।", - "description": "helper for inputting hex as decimal input" - }, "blockExplorerUrl": { "message": "एक्सप्लोरर को ब्लॉक करें" }, @@ -252,9 +236,6 @@ "connect": { "message": "कनेक्ट करें" }, - "connectRequest": { - "message": "संपर्क अनुरोध" - }, "connectingTo": { "message": "$1 से कनेक्ट हो रहा है" }, @@ -294,9 +275,6 @@ "copiedExclamation": { "message": "कॉपी की गई!" }, - "copy": { - "message": "कॉपी बनाएं" - }, "copyAddress": { "message": "क्लिपबोर्ड पर पता कॉपी करें" }, @@ -375,9 +353,6 @@ "directDepositEtherExplainer": { "message": "यदि आपके पास पहले से ही कुछ Ether हैं, तो अपने नए वॉलेट में Ether पाने का सबसे तेज़ तरीका सीधे जमा करना है।" }, - "dismiss": { - "message": "खारिज करें" - }, "done": { "message": "पूर्ण" }, @@ -543,10 +518,6 @@ "getStarted": { "message": "आरंभ करें" }, - "greaterThanMin": { - "message": "$1 से बड़ा या उसके बराबर होना चाहिए।", - "description": "helper for inputting hex as decimal input" - }, "happyToSeeYou": { "message": "आपको देखकर हमें बहुत ख़ुशी हुई।" }, @@ -665,10 +636,6 @@ "ledgerAccountRestriction": { "message": "नया खाता जोड़ने से पहले आपको अपने पिछले खाते का उपयोग करना होगा।" }, - "lessThanMax": { - "message": "$1 से कम या उसके बराबर होना चाहिए।", - "description": "helper for inputting hex as decimal input" - }, "letsGoSetUp": { "message": "जी हां, सेट होने दें!" }, @@ -867,9 +834,6 @@ "message": "अपनी निजी कुंजी स्ट्रिंग यहां पेस्ट करें:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "अपना बीज वाक्यांश यहाँ पेस्ट करें!" - }, "pending": { "message": "विलंबित" }, @@ -1180,9 +1144,6 @@ "storePhrase": { "message": "इस वाक्यांश को 1पासवर्ड जैसे पासवर्ड मैनेजर में संग्रहीत करें।" }, - "submit": { - "message": "सबमिट करें" - }, "submitted": { "message": "प्रस्तुत किया गया" }, @@ -1350,9 +1311,6 @@ "userName": { "message": "उपयोगकर्ता नाम" }, - "validFileImport": { - "message": "आयात करने के लिए आपको एक मान्य फ़ाइल को चुनना होगा।" - }, "viewAccount": { "message": "खाता देखें" }, diff --git a/app/_locales/hn/messages.json b/app/_locales/hn/messages.json index 91d9bde7e..800907798 100644 --- a/app/_locales/hn/messages.json +++ b/app/_locales/hn/messages.json @@ -1,19 +1,10 @@ { - "confirmClear": { - "message": "क्या आप वाकई अनुमोदित वेबसाइटों को साफ़ करना चाहते हैं?" - }, - "clearApprovalData": { - "message": "अनुमोदन डेटा साफ़ करें" - }, "approve": { "message": "मंजूर" }, "reject": { "message": "अस्वीकार" }, - "providerRequestInfo": { - "message": "नीचे सूचीबद्ध डोमेन वेब 3 एपीआई तक पहुंच का अनुरोध करने का प्रयास कर रहा है ताकि यह एथेरियम ब्लॉकचेन से बातचीत कर सके। वेब 3 एक्सेस को मंजूरी देने से पहले हमेशा सही जांच करें कि आप सही साइट पर हैं।" - }, "account": { "message": "खाता" }, @@ -55,10 +46,6 @@ "balanceIsInsufficientGas": { "message": "वर्तमान गैस कुल के लिए अपर्याप्त शेष" }, - "betweenMinAndMax": { - "message": "$1 के बराबर या ज्यदा या, $2 के बराबर या कम होना चाहिए।", - "description": "हेक्स इनपुट के लिए दशमलव इनपुट के रूप में सहायक" - }, "blockiesIdenticon": { "message": "ब्लॉकीज पहचान का उपयोग करें" }, @@ -95,9 +82,6 @@ "copiedExclamation": { "message": "कॉपी कर दिया गया!" }, - "copy": { - "message": "कॉपी / प्रतिलिपि कर्रे" - }, "copyToClipboard": { "message": "क्लिपबोर्ड पर कॉपी करें" }, @@ -203,10 +187,6 @@ "message": "$1 के लिए एक नल से ईथर प्राप्त करें", "description": "ईथर नल के लिए नेटवर्क नाम प्रदर्शित करता है" }, - "greaterThanMin": { - "message": "$1 के बराबर या बराबर होना चाहिए।", - "description": "हेक्स इनपुट के लिए दशमलव इनपुट के रूप में सहायक" - }, "here": { "message": "यहां", "description": "अधिक जानकारी के लिए यहां क्लिक करें- (परेशानी के साथ जाता है टोकनबैलेंस) (troubleTokenBalances)" @@ -265,10 +245,6 @@ "kovan": { "message": "कोवान टेस्ट नेटवर्क" }, - "lessThanMax": { - "message": "$1 से कम या बराबर होना चाहिए।", - "description": "हेक्स इनपुट के लिए दशमलव इनपुट के रूप में सहायक" - }, "likeToAddTokens": { "message": "क्या आप इन टोकनों को जोड़ना चाहते हैं?" }, @@ -344,9 +320,6 @@ "message": "यहां अपनी निजी कुंजी स्ट्रिंग चिपकाएं:", "description": "किसी निजी कुंजी से किसी खाते को आयात करने के लिए" }, - "pasteSeed": { - "message": "यहां अपने बीज वाक्यांश पेस्ट करें!" - }, "personalAddressDetected": { "message": "व्यक्तिगत पता मिला। टोकन अनुबंध का पता इनपुट।" }, @@ -447,9 +420,6 @@ "stateLogsDescription": { "message": "स्थिति संदेश में आपका सार्वजनिक खाता, पतों और भेजे गए लेनदेन, होते हैं।" }, - "submit": { - "message": "सबमिट करें" - }, "supportCenter": { "message": "हमारे सहायता केंद्र पर जाएं" }, @@ -497,9 +467,6 @@ "usedByClients": { "message": "विभिन्न क्लाइंट्स द्वारा उपयोग किया जाता है" }, - "validFileImport": { - "message": "आयात करने के लिए आपको एक वैध फ़ाइल चुननी होगी।" - }, "viewAccount": { "message": "खाता देखें" }, diff --git a/app/_locales/hr/messages.json b/app/_locales/hr/messages.json index ba64748c2..57b4b0d4c 100644 --- a/app/_locales/hr/messages.json +++ b/app/_locales/hr/messages.json @@ -1,19 +1,10 @@ { - "privacyModeDefault": { - "message": "Način se Privatnost sada zadano omogućava" - }, "chartOnlyAvailableEth": { "message": "Grafikon je dostupan samo na mrežama Ethereum." }, - "confirmClear": { - "message": "Sigurno želite očistiti odobrena mrežna mjesta?" - }, "contractInteraction": { "message": "Ugovorna interakcija" }, - "clearApprovalData": { - "message": "Očisti podatke o privatnosti" - }, "appName": { "message": "MetaMask", "description": "The name of the application" @@ -21,17 +12,14 @@ "reject": { "message": "Odbaci" }, - "providerRequest": { + "likeToConnect": { "message": "Korisnik $1 želi se povezati na vaš račun" }, - "providerRequestInfo": { - "message": "Na ovom se mjestu zahtijeva pristup za pregledavanje vaše trenutačne adrese računa. Uvijek pazite da vjerujete mrežnim mjestima s kojima rukujete." - }, "about": { "message": "O opcijama" }, "aboutSettingsDescription": { - "message": "Inačica, centar za podršku i informacije za kontakt." + "message": "Inačica, centar za podršku i informacije za kontakt" }, "acceleratingATransaction": { "message": "* Ubrzavanjem se transakcije pomoću veće cijene goriva povećava šansa za bržu obradu mrežom, ali se uvijek ne jamči." @@ -67,7 +55,7 @@ "message": "Napredno" }, "advancedSettingsDescription": { - "message": "Pristup značajkama razvojnog inženjera, preuzimanje zapisnika stanja, poništavanje računa, postavljanje testnih mreža i prilagođeni RPC." + "message": "Pristup značajkama razvojnog inženjera, preuzimanje zapisnika stanja, poništavanje računa, postavljanje testnih mreža i prilagođeni RPC" }, "advancedOptions": { "message": "Napredne mogućnosti" @@ -157,10 +145,6 @@ "basic": { "message": "Osnovne" }, - "betweenMinAndMax": { - "message": "mora biti veće od $1 ili jednako te manje od $2 ili jednako.", - "description": "helper for inputting hex as decimal input" - }, "blockExplorerUrl": { "message": "Blokiraj Explorer" }, @@ -252,9 +236,6 @@ "connect": { "message": "Povežite se" }, - "connectRequest": { - "message": "Zahtjev za povezivanjem" - }, "connectingTo": { "message": "Povezivanje na $1" }, @@ -294,9 +275,6 @@ "copiedExclamation": { "message": "Kopirano!" }, - "copy": { - "message": "Kopiraj" - }, "copyAddress": { "message": "Kopiraj adresu u međuspremnik" }, @@ -375,9 +353,6 @@ "directDepositEtherExplainer": { "message": "Ako imate nešto Ethera, najbrži je način prebacivanja Ethera u vaš novi novčanik izravan polog." }, - "dismiss": { - "message": "Odbaci" - }, "done": { "message": "Gotovo" }, @@ -539,10 +514,6 @@ "getStarted": { "message": "Početak upotrebe" }, - "greaterThanMin": { - "message": "mora biti veće od $1 ili jednako.", - "description": "helper for inputting hex as decimal input" - }, "happyToSeeYou": { "message": "Sretni smo što ste tu." }, @@ -661,10 +632,6 @@ "ledgerAccountRestriction": { "message": "Treba se koristiti zadnjim računom kako biste dodali novi račun." }, - "lessThanMax": { - "message": "mora biti manje od $1 ili jednako.", - "description": "helper for inputting hex as decimal input" - }, "letsGoSetUp": { "message": "Da, obavimo postavljanje!" }, @@ -863,9 +830,6 @@ "message": "Ovdje zalijepite svoj privatni niz ključa:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "Ovdje zalijepite svoju početnu rečenicu!" - }, "pending": { "message": "na čekanju" }, @@ -1179,9 +1143,6 @@ "storePhrase": { "message": "Spremite ovu rečenicu u upravitelj lozinkama poput aplikacije 1Password." }, - "submit": { - "message": "Pošalji" - }, "submitted": { "message": "Poslano" }, @@ -1346,9 +1307,6 @@ "userName": { "message": "Korisničko ime" }, - "validFileImport": { - "message": "Morate odabrati valjanu datoteku za uvoz." - }, "viewAccount": { "message": "Prikaz računa" }, diff --git a/app/_locales/ht/messages.json b/app/_locales/ht/messages.json index d973aeef5..dd63dce0c 100644 --- a/app/_locales/ht/messages.json +++ b/app/_locales/ht/messages.json @@ -1,13 +1,4 @@ { - "confirmClear": { - "message": "Èske ou sèten ou vle klè sitwèb apwouve?" - }, - "clearApprovalData": { - "message": "Klè Done sou vi prive" - }, - "providerRequestInfo": { - "message": "Domèn ki nan lis anba a ap mande pou jwenn aksè a blòkchou Ethereum ak pou wè kont ou ye kounye a. Toujou double tcheke ke ou sou sit ki kòrèk la anvan apwouve aksè." - }, "accessingYourCamera": { "message": "Aksè a Kamera" }, @@ -79,10 +70,6 @@ "balanceIsInsufficientGas": { "message": "Ensifizan balans pou total gaz aktyèl la" }, - "betweenMinAndMax": { - "message": "dwe plis pase oswa egal a $ 1 mwens ke oswa egal a $ 2.", - "description": "helper for inputting hex as decimal input" - }, "blockiesIdenticon": { "message": "Itilize Blockies Identicon" }, @@ -161,9 +148,6 @@ "copiedExclamation": { "message": "Kopye!" }, - "copy": { - "message": "Kopye" - }, "copyAddress": { "message": "Kopi adrès clipboard" }, @@ -305,10 +289,6 @@ "getHelp": { "message": "Jwenn èd." }, - "greaterThanMin": { - "message": "dwe pi gran pase oswa egal a $ 1.", - "description": "helper for inputting hex as decimal input" - }, "hardware": { "message": "materyèl" }, @@ -403,10 +383,6 @@ "ledgerAccountRestriction": { "message": "Ou bezwen sèvi ak dènye kont ou anvan ou ka ajoute yon nouvo." }, - "lessThanMax": { - "message": "dwe mwens pase oswa egal a $ 1.", - "description": "helper for inputting hex as decimal input" - }, "likeToAddTokens": { "message": "Èske ou ta renmen ajoute sa nan tokens?" }, @@ -524,9 +500,6 @@ "message": "Kole fraz prive ou a la:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "Kole seed fraz ou a la!" - }, "pending": { "message": "l ap mache" }, @@ -756,9 +729,6 @@ "step3HardwareWalletMsg": { "message": "Sèvi ak kont materyèl ou menm jan ou t ap fè pou kont Etherum. Ouvri sesyon an nan dApps, voye Eth, achte ak stòke ERC20 tokens ak e ki pake chanje tokens tankou CryptoKitties." }, - "submit": { - "message": "Soumèt" - }, "submitted": { "message": "Te Soumèt" }, @@ -857,9 +827,6 @@ "usedByClients": { "message": "Itilize pa yon varyete de kliyan diferan" }, - "validFileImport": { - "message": "Ou dwe chwazi yon dosye ki valab pou enpòte." - }, "viewAccount": { "message": "Wè Kont" }, diff --git a/app/_locales/hu/messages.json b/app/_locales/hu/messages.json index 542534f32..33b8dac32 100644 --- a/app/_locales/hu/messages.json +++ b/app/_locales/hu/messages.json @@ -1,19 +1,10 @@ { - "privacyModeDefault": { - "message": "Az adatvédelmi mód mostantól alapbeállításként engedélyezve van" - }, "chartOnlyAvailableEth": { "message": "A diagram csak Ethereum hálózatokon érhető el" }, - "confirmClear": { - "message": "Biztosan törölni szeretnéd a jóváhagyott weboldalakat?" - }, "contractInteraction": { "message": "Szerződéses interakció" }, - "clearApprovalData": { - "message": "Adatvédelmi adatok törlése" - }, "appName": { "message": "MetaMask", "description": "The name of the application" @@ -21,17 +12,14 @@ "reject": { "message": "Elutasítás" }, - "providerRequest": { + "likeToConnect": { "message": "$1 szeretne kapcsolódni az ön fiókjához" }, - "providerRequestInfo": { - "message": "A webhely hozzáférést kér az ön jelenlegi fiókcímének megtekintéséhez. Mindig győződjön meg arról, hogy megbízható webhellyel létesít kapcsolatot." - }, "about": { "message": "Névjegy" }, "aboutSettingsDescription": { - "message": "Verzió, ügyfélszolgálat és elérhetőségek." + "message": "Verzió, ügyfélszolgálat és elérhetőségek" }, "acceleratingATransaction": { "message": "* Ha szeretné felgyorsítani a tranzakciót azzal, hogy magasabb gázárat használ, az növeli a gyorsabb feldolgozás esélyét, de ez nem mindig garantált." @@ -67,7 +55,7 @@ "message": "Speciális" }, "advancedSettingsDescription": { - "message": "Hozzáférés fejlesztői funkciókhoz, állapotnaplók letöltése, fiók újraállítása, testnetek és egyéni RPC-k beállítása." + "message": "Hozzáférés fejlesztői funkciókhoz, állapotnaplók letöltése, fiók újraállítása, testnetek és egyéni RPC-k beállítása" }, "advancedOptions": { "message": "Haladó beállítások" @@ -157,10 +145,6 @@ "basic": { "message": "Alapvető" }, - "betweenMinAndMax": { - "message": "legyen nagyobb vagy egyenlő mint $1 és kisebb vagy egyenlő mint $2.", - "description": "helper for inputting hex as decimal input" - }, "blockExplorerUrl": { "message": "Explorer blokkolása" }, @@ -252,9 +236,6 @@ "connect": { "message": "Csatlakozás" }, - "connectRequest": { - "message": "Csatlakozási kérelem" - }, "connectingTo": { "message": "Kapcsolódás: $1" }, @@ -294,9 +275,6 @@ "copiedExclamation": { "message": "Kimásolva!" }, - "copy": { - "message": "Másolás" - }, "copyAddress": { "message": "Másolja a címet a vágólapra" }, @@ -375,9 +353,6 @@ "directDepositEtherExplainer": { "message": "Amennyiben már rendelkezik némi Ether-rel, a közvetlen letéttel gyorsan elhelyezheti azt új pénztárcájában." }, - "dismiss": { - "message": "Elvetés" - }, "done": { "message": "Kész" }, @@ -539,10 +514,6 @@ "getStarted": { "message": "Első lépések" }, - "greaterThanMin": { - "message": "legyen nagyobb vagy egyenlő mint $1.", - "description": "helper for inputting hex as decimal input" - }, "happyToSeeYou": { "message": "Örülünk, hogy itt van. " }, @@ -661,10 +632,6 @@ "ledgerAccountRestriction": { "message": "Használnia kell a korábbi fiókját, mielőtt újat adhat hozzá. " }, - "lessThanMax": { - "message": "egyenlő vagy kevesebb kell legyen mint $1.", - "description": "helper for inputting hex as decimal input" - }, "letsGoSetUp": { "message": "Igen, hozzuk létre!" }, @@ -863,9 +830,6 @@ "message": "Illessze be ide a privát kulcs karakterláncát:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "Másolja be ide a seed mondatot!" - }, "pending": { "message": "folyamatban" }, @@ -1179,9 +1143,6 @@ "storePhrase": { "message": "Tárolja a mondatot jelszókezelőben, például az 1Passwordben." }, - "submit": { - "message": "Elküldés" - }, "submitted": { "message": "Elküldve" }, @@ -1346,9 +1307,6 @@ "userName": { "message": "Felhasználónév" }, - "validFileImport": { - "message": "Ki kell választanod egy érvényes fájlt az importáláshoz." - }, "viewAccount": { "message": "Fiók megtekintése" }, diff --git a/app/_locales/id/messages.json b/app/_locales/id/messages.json index 252b7ec9f..874ca4576 100644 --- a/app/_locales/id/messages.json +++ b/app/_locales/id/messages.json @@ -1,19 +1,10 @@ { - "privacyModeDefault": { - "message": "Modus Privasi kini aktif secara default" - }, "chartOnlyAvailableEth": { "message": "Grafik hanya tersedia pada jaringan Ethereum." }, - "confirmClear": { - "message": "Yakin ingin mengosongkan website yang disetujui?" - }, "contractInteraction": { "message": "Interaksi Kontrak" }, - "clearApprovalData": { - "message": "Bersihkan Data Privasi" - }, "appName": { "message": "MetaMask", "description": "The name of the application" @@ -21,17 +12,14 @@ "reject": { "message": "Tolak" }, - "providerRequest": { + "likeToConnect": { "message": "$1 ingin menghubungkan ke akun Anda" }, - "providerRequestInfo": { - "message": "Situs ini meminta akses untuk melihat alamat akun Anda saat ini. Selalu pastikan bahwa Anda bisa mempercayai situs yang berinteraksi dengan Anda." - }, "about": { "message": "Tentang" }, "aboutSettingsDescription": { - "message": "Versi, pusat dukungan, dan informasi kontak." + "message": "Versi, pusat dukungan, dan informasi kontak" }, "acceleratingATransaction": { "message": "* Mempercepat transaksi dengan menggunakan harga gas yang lebih tinggi meningkatkan peluangnya untuk lebih cepat diproses oleh jaringan, tetapi tak selalu terjamin pasti cepat." @@ -67,7 +55,7 @@ "message": "Lanjutan" }, "advancedSettingsDescription": { - "message": "Akses fitur pengembang, unduh Log Status, Atur Ulang Akun, tata testnets dan RPC kustom." + "message": "Akses fitur pengembang, unduh Log Status, Atur Ulang Akun, tata testnets dan RPC kustom" }, "advancedOptions": { "message": "Opsi Lanjutan" @@ -157,10 +145,6 @@ "basic": { "message": "Dasar" }, - "betweenMinAndMax": { - "message": "harus lebih dari atau sama dengan $1 dan kurang dari atau sama dengan $2.", - "description": "helper for inputting hex as decimal input" - }, "blockExplorerUrl": { "message": "Blokir Penjelajah" }, @@ -252,9 +236,6 @@ "connect": { "message": "Sambungkan" }, - "connectRequest": { - "message": "Permintaan Sambungan" - }, "connectingTo": { "message": "Menghubungkan ke $1" }, @@ -294,9 +275,6 @@ "copiedExclamation": { "message": "Disalin!" }, - "copy": { - "message": "Salin" - }, "copyAddress": { "message": "Salin alamat ke clipboard" }, @@ -369,9 +347,6 @@ "directDepositEtherExplainer": { "message": "Jika Anda sudah memiliki Ether, cara tercepat mendapatkan Ether di dompet baru lewat deposit langsung." }, - "dismiss": { - "message": "Tutup" - }, "done": { "message": "Selesai" }, @@ -530,10 +505,6 @@ "getStarted": { "message": "Mulai" }, - "greaterThanMin": { - "message": "harus lebih besar atau sama dengan $1.", - "description": "helper for inputting hex as decimal input" - }, "happyToSeeYou": { "message": "Kami senang bertemu dengan Anda." }, @@ -652,10 +623,6 @@ "ledgerAccountRestriction": { "message": "Anda perlu menggunakan akun terakhir sebelum dapat menambahkan akun baru." }, - "lessThanMax": { - "message": "harus kurang dari atau setara $1.", - "description": "helper for inputting hex as decimal input" - }, "letsGoSetUp": { "message": "Ya, ayo tata!" }, @@ -848,9 +815,6 @@ "message": "Tempelkan string kunci privat di sini:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "Tempel frasa benih Anda di sini!" - }, "pending": { "message": "tertunda" }, @@ -1164,9 +1128,6 @@ "storePhrase": { "message": "Simpan frasa ini dalam manajer sandi seperti 1Password." }, - "submit": { - "message": "Kirim" - }, "submitted": { "message": "Terkirim" }, @@ -1325,9 +1286,6 @@ "userName": { "message": "Nama Pengguna" }, - "validFileImport": { - "message": "Anda harus memilih berkas yang sah untuk diimpor." - }, "viewAccount": { "message": "Lihat Akun" }, diff --git a/app/_locales/it/messages.json b/app/_locales/it/messages.json index 1d0725183..213407a3b 100644 --- a/app/_locales/it/messages.json +++ b/app/_locales/it/messages.json @@ -1,33 +1,21 @@ { - "privacyModeDefault": { - "message": "La modalità privacy è ora abilitata per impostazione predefinita" - }, "chartOnlyAvailableEth": { "message": "Grafico disponibile solo per le reti Ethereum." }, - "confirmClear": { - "message": "Sei sicuro di voler cancellare i siti Web approvati?" - }, "contractInteraction": { "message": "Interazione Contratto" }, - "clearApprovalData": { - "message": "Cancella i dati di approvazione" - }, "reject": { "message": "Annulla" }, - "providerRequest": { + "likeToConnect": { "message": "$1 vorrebbe connettersi al tuo account" }, - "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." + "message": "Version, centro di supporto e contatti" }, "acceleratingATransaction": { "message": "* Accelerare una transazione usando un prezzo del gas maggiore aumenta la probabilità che la rete la elabori più velocemente, ma non è garantito." @@ -60,7 +48,7 @@ "message": "Avanzate" }, "advancedSettingsDescription": { - "message": "Accedi alle funzionalità sviluppatore, download dei log di Stato, Reset Account, imposta reti di test e RPC personalizzata." + "message": "Accedi alle funzionalità sviluppatore, download dei log di Stato, Reset Account, imposta reti di test e RPC personalizzata" }, "advancedOptions": { "message": "Opzioni Avanzate" @@ -151,10 +139,6 @@ "basic": { "message": "Base" }, - "betweenMinAndMax": { - "message": "deve essere maggiore o uguale a $1 e minore o uguale a $2.", - "description": "helper for inputting hex as decimal input" - }, "blockExplorerView": { "message": "Visualizza account su $1", "description": "$1 replaced by URL for custom block explorer" @@ -231,9 +215,6 @@ "connect": { "message": "Connetti" }, - "connectRequest": { - "message": "Richiesta Connessione" - }, "connectingTo": { "message": "Connessione in corso a $1" }, @@ -273,9 +254,6 @@ "copiedExclamation": { "message": "Copiato!" }, - "copy": { - "message": "Copia" - }, "copyAddress": { "message": "Copia l'indirizzo" }, @@ -354,9 +332,6 @@ "directDepositEtherExplainer": { "message": "Se possiedi già degli Ether, questa è la via più veloce per aggiungere Ether al tuo portafoglio con un deposito diretto." }, - "dismiss": { - "message": "Ignora" - }, "done": { "message": "Finito" }, @@ -522,10 +497,6 @@ "getStarted": { "message": "Inizia" }, - "greaterThanMin": { - "message": "deve essere maggiore o uguale a $1.", - "description": "helper for inputting hex as decimal input" - }, "happyToSeeYou": { "message": "Siamo contenti di vederti." }, @@ -641,10 +612,6 @@ "ledgerAccountRestriction": { "message": "E' necessario utilizzare l'ultimo account prima di poterne aggiungere uno nuovo." }, - "lessThanMax": { - "message": "deve essere minore o uguale a $1.", - "description": "helper for inputting hex as decimal input" - }, "letsGoSetUp": { "message": "Si, iniziamo!" }, @@ -833,9 +800,6 @@ "message": "Incolla la tua chiave privata qui:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "Incolla la tua frase seed qui!" - }, "pending": { "message": "in corso" }, @@ -1149,9 +1113,6 @@ "storePhrase": { "message": "Conserva questa frase in un gestore di password come 1Password." }, - "submit": { - "message": "Invia" - }, "submitted": { "message": "Inviata" }, @@ -1316,9 +1277,6 @@ "userName": { "message": "Nome utente" }, - "validFileImport": { - "message": "Devi selezionare un file valido da importare." - }, "viewAccount": { "message": "Vedi Account" }, @@ -1367,24 +1325,6 @@ "zeroGasPriceOnSpeedUpError": { "message": "Prezzo del gas maggiore di zero" }, - "connections": { - "message": "Connessioni" - }, - "connectionsSettingsDescription": { - "message": "Siti autorizzati ad accedere ai tuoi accounts" - }, - "addSite": { - "message": "Aggiungi Sito" - }, - "addSiteDescription": { - "message": "Aggiungi un sito autorizzato ad accedere ai tuoi accounts, utile per dapps obsolete" - }, - "connected": { - "message": "Connesso" - }, - "connectedDescription": { - "message": "La lista di siti web autorizzati ad accedere ai tuoi indirizzi" - }, "contacts": { "message": "Contatti" }, diff --git a/app/_locales/ja/messages.json b/app/_locales/ja/messages.json index 5b85291b9..18dfb068e 100644 --- a/app/_locales/ja/messages.json +++ b/app/_locales/ja/messages.json @@ -1,28 +1,16 @@ { - "privacyModeDefault": { - "message": "プライバシーモードがデフォルトで有効になりました" - }, "chartOnlyAvailableEth": { "message": "チャートはEthereumネットワークでのみ利用可能です。" }, - "confirmClear": { - "message": "承認されたウェブサイトをクリアしてもよろしいですか?" - }, "contractInteraction": { "message": "コントラクトへのアクセス" }, - "clearApprovalData": { - "message": "承認データのクリア" - }, "reject": { "message": "拒否" }, - "providerRequest": { + "likeToConnect": { "message": "$1 はあなたのアカウントにアクセスしようとしています。" }, - "providerRequestInfo": { - "message": "下記のドメインは、Ethereumブロックチェーンとやり取りできるようにEthereum APIへのアクセスをリクエストしようとしています。 Web3アクセスを承認する前に、正しいサイトにいることを常に確認してください。" - }, "aboutSettingsDescription": { "message": "バージョンやサポート、問合せ先など" }, @@ -84,7 +72,7 @@ "message": "推奨トークンを追加" }, "addAcquiredTokens": { - "message": "Metamaskで獲得したトークンを追加する" + "message": "MetaMaskで獲得したトークンを追加する" }, "amount": { "message": "金額" @@ -136,10 +124,6 @@ "balanceIsInsufficientGas": { "message": "現在のガス総量に対して残高が不足しています" }, - "betweenMinAndMax": { - "message": " $1以上 $2以下にして下さい。", - "description": "helper for inputting hex as decimal input" - }, "blockiesIdenticon": { "message": "Blockies Identicon を使用" }, @@ -173,9 +157,6 @@ "copiedExclamation": { "message": "コピー完了!" }, - "copy": { - "message": "コピー" - }, "copyToClipboard": { "message": "クリップボードへコピー" }, @@ -278,10 +259,6 @@ "message": "フォーセットで $1のEtherを得ることができます。", "description": "Displays network name for Ether faucet" }, - "greaterThanMin": { - "message": " $1以上にして下さい。", - "description": "helper for inputting hex as decimal input" - }, "here": { "message": "ここ", "description": "as in -click here- for more information (goes with troubleTokenBalances)" @@ -334,10 +311,6 @@ "learnMore": { "message": "詳細" }, - "lessThanMax": { - "message": " $1以下にして下さい。", - "description": "helper for inputting hex as decimal input" - }, "likeToAddTokens": { "message": "トークンを追加しますか?" }, @@ -416,9 +389,6 @@ "message": "秘密鍵をここにペーストして下さい:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "パスフレーズをここにペーストして下さい!" - }, "privacyMsg": { "message": "プライバシーポリシー" }, @@ -510,9 +480,6 @@ "sigRequest": { "message": "署名リクエスト" }, - "submit": { - "message": "送信" - }, "terms": { "message": "利用規約" }, diff --git a/app/_locales/kn/messages.json b/app/_locales/kn/messages.json index 7fc114988..7f3dd0f17 100644 --- a/app/_locales/kn/messages.json +++ b/app/_locales/kn/messages.json @@ -1,19 +1,10 @@ { - "privacyModeDefault": { - "message": "ಗೌಪ್ಯತೆ ಮೋಡ್ ಅನ್ನು ಡೀಫಾಲ್ಟ್‌ ಆಗಿ ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ" - }, "chartOnlyAvailableEth": { "message": "ಎಥೆರಿಯಮ್ ನೆಟ್‌ವರ್ಕ್‌ಗಳಲ್ಲಿ ಮಾತ್ರವೇ ಚಾರ್ಟ್‌ಗಳು ಲಭ್ಯವಿರುತ್ತವೆ." }, - "confirmClear": { - "message": "ನೀವು ಅನುಮೋದಿಸಿದ ವೆಬ್‌‌ಸೈಟ್‌ಗಳನ್ನು ತೆರವುಗೊಳಿಸಲು ಬಯಸುವಿರಾ?" - }, "contractInteraction": { "message": "ಒಪ್ಪಂದದ ಸಂವಹನ" }, - "clearApprovalData": { - "message": "ಗೌಪ್ಯತೆ ಡೇಟಾವನ್ನು ತೆರವುಗೊಳಿಸಿ" - }, "appName": { "message": "MetaMask", "description": "The name of the application" @@ -21,17 +12,14 @@ "reject": { "message": "ತಿರಸ್ಕರಿಸಿ" }, - "providerRequest": { + "likeToConnect": { "message": "$1 ನಿಮ್ಮ ಖಾತೆಗೆ ಸಂಪರ್ಕಿಸಲು ಬಯಸುತ್ತಿದೆ" }, - "providerRequestInfo": { - "message": "ಈ ಸೈಟ್ ನಿಮ್ಮ ಪ್ರಸ್ತುತ ಖಾತೆ ವಿಳಾಸವನ್ನು ವೀಕ್ಷಿಸಲು ಪ್ರವೇಶವನ್ನು ವಿನಂತಿಸುತ್ತಿದೆ. ನೀವು ಸಂವಹನ ನಡೆಸುವ ಸೈಟ್‌ಗಳನ್ನು ನೀವು ನಂಬಿರುವಿರಿ ಎಂಬುದನ್ನು ಯಾವಾಗಲೂ ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಿ." - }, "about": { "message": "ಕುರಿತು" }, "aboutSettingsDescription": { - "message": "ಆವೃತ್ತಿ, ಬೆಂಬಲ ಕೇಂದ್ರ ಮತ್ತು ಸಂಪರ್ಕ ಮಾಹಿತಿ." + "message": "ಆವೃತ್ತಿ, ಬೆಂಬಲ ಕೇಂದ್ರ ಮತ್ತು ಸಂಪರ್ಕ ಮಾಹಿತಿ" }, "acceleratingATransaction": { "message": "* ಹೆಚ್ಚಿನ ಗ್ಯಾಸ್ ಬೆಲೆಯನ್ನು ಬಳಸಿಕೊಂಡು ವಹಿವಾಟನ್ನು ವೇಗಗೊಳಿಸುವುದರಿಂದ ನೆಟ್‌ವರ್ಕ್ ವೇಗವಾಗಿ ಪ್ರಕ್ರಿಯೆಗೊಳ್ಳುವ ಸಾಧ್ಯತೆಗಳನ್ನು ಅದು ಹೆಚ್ಚಿಸುತ್ತದೆ, ಆದರೆ ಇದು ಯಾವಾಗಲೂ ಖಚಿತವಾಗಿರುವುದಿಲ್ಲ." @@ -67,7 +55,7 @@ "message": "ಸುಧಾರಿತ" }, "advancedSettingsDescription": { - "message": "ಡೆವಲಪರ್ ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ಪ್ರವೇಶಿಸಿ, ರಾಜ್ಯದ ಲಾಗ್‌ಗಳನ್ನು ಡೌನ್‌ಲೋಡ್ ಮಾಡಿ, ಖಾತೆಯನ್ನು ಮರುಹೊಂದಿಸಿ, ಟೆಸ್ಟ್‌ನೆಟ್ಸ್‌ ಹೊಂದಿಸಿ ಮತ್ತು ಕಸ್ಟಮ್ RPC." + "message": "ಡೆವಲಪರ್ ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ಪ್ರವೇಶಿಸಿ, ರಾಜ್ಯದ ಲಾಗ್‌ಗಳನ್ನು ಡೌನ್‌ಲೋಡ್ ಮಾಡಿ, ಖಾತೆಯನ್ನು ಮರುಹೊಂದಿಸಿ, ಟೆಸ್ಟ್‌ನೆಟ್ಸ್‌ ಹೊಂದಿಸಿ ಮತ್ತು ಕಸ್ಟಮ್ RPC" }, "advancedOptions": { "message": "ಸುಧಾರಿತ ಆಯ್ಕೆಗಳು" @@ -157,10 +145,6 @@ "basic": { "message": "ಮೂಲ" }, - "betweenMinAndMax": { - "message": " $1 ಗಿಂತಲೂ ಹೆಚ್ಚಾಗಿರಬೇಕು ಅಥವಾ ಸಮನಾಗಿರಬೇಕು ಮತ್ತು $2 ಗಿಂತಲೂ ಕಡಿಮೆ ಇರಬೇಕು ಅಥವಾ ಸಮನಾಗಿರಬೇಕು.", - "description": "helper for inputting hex as decimal input" - }, "blockExplorerUrl": { "message": "ಅನ್ವೇಷಕವನ್ನು ನಿರ್ಬಂಧಿಸಿ" }, @@ -252,9 +236,6 @@ "connect": { "message": "ಸಂಪರ್ಕಿಸು" }, - "connectRequest": { - "message": "ವಿನಂತಿಯನ್ನು ಸಂಪರ್ಕಪಡಿಸಿ" - }, "connectingTo": { "message": "$1 ಗೆ ಸಂಪರ್ಕಪಡಿಸಲಾಗುತ್ತಿದೆ" }, @@ -294,9 +275,6 @@ "copiedExclamation": { "message": "ನಕಲಿಸಲಾಗಿದೆ!" }, - "copy": { - "message": "ನಕಲಿಸು" - }, "copyAddress": { "message": "ವಿಳಾಸವನ್ನು ಕ್ಲಿಪ್‌ಬೋರ್ಡ್‌ಗೆ ನಕಲಿಸಿ" }, @@ -375,9 +353,6 @@ "directDepositEtherExplainer": { "message": "ನೀವು ಈಗಾಗಲೇ ಕೆಲವು ಎಥರ್ ಹೊಂದಿದ್ದರೆ, ನೇರ ಠೇವಣಿ ಮೂಲಕ ನಿಮ್ಮ ಹೊಸ ವ್ಯಾಲೆಟ್‌ನಲ್ಲಿ ಎಥರ್ ಅನ್ನು ಪಡೆಯುವ ತ್ವರಿತ ಮಾರ್ಗ." }, - "dismiss": { - "message": "ವಜಾಗೊಳಿಸಿ" - }, "done": { "message": "ಮುಗಿದಿದೆ" }, @@ -543,10 +518,6 @@ "getStarted": { "message": "ಪ್ರಾರಂಭಗೊಂಡಿದೆ" }, - "greaterThanMin": { - "message": " $1 ಗಿಂತಲೂ ಹೆಚ್ಚಾಗಿರಬೇಕು ಅಥವಾ ಸಮನಾಗಿರಬೇಕು.", - "description": "helper for inputting hex as decimal input" - }, "happyToSeeYou": { "message": "ನಿಮ್ಮನ್ನು ನೋಡಿ ನಮಗೆ ಸಂತೋಷವಾಗಿದೆ." }, @@ -665,10 +636,6 @@ "ledgerAccountRestriction": { "message": "ನೀವು ಹೊಸದನ್ನು ಸೇರಿಸುವುದರ ಮೊದಲು ನಿಮ್ಮ ಹಿಂದಿನ ಖಾತೆಯನ್ನು ನೀವು ಬಳಸಬೇಕು." }, - "lessThanMax": { - "message": "$1 ಗಿಂತ ಕಡಿಮೆ ಅಥವಾ ಸಮನಾಗಿರಬೇಕು.", - "description": "helper for inputting hex as decimal input" - }, "letsGoSetUp": { "message": "ಹೌದು, ಹೊಂದಿಸೋಣ!" }, @@ -870,9 +837,6 @@ "message": "ನಿಮ್ಮ ಖಾಸಗಿ ಪ್ರಮುಖ ಸ್ಟ್ರಿಂಗ್ ಅನ್ನು ಇಲ್ಲಿ ನಕಲಿಸಿ:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "ನಿಮ್ಮ ಸೀಡ್ ಫ್ರೇಸ್ ಅನ್ನು ಇಲ್ಲಿ ನಕಲಿಸಿ!" - }, "pending": { "message": "ಬಾಕಿಯಿರುವುದು" }, @@ -1186,9 +1150,6 @@ "storePhrase": { "message": "ಈ ಫ್ರೇಸ್ ಅನ್ನು ಪಾಸ್‌ವರ್ಡ್ ನಿರ್ವಾಹಕದಲ್ಲಿ 1Password ರೂಪದಲ್ಲಿ ಸಂಗ್ರಹಿಸಿ." }, - "submit": { - "message": "ಸಲ್ಲಿಸು" - }, "submitted": { "message": "ಸಲ್ಲಿಸಲಾಗಿದೆ" }, @@ -1356,9 +1317,6 @@ "userName": { "message": "ಬಳಕೆದಾರಹೆಸರು" }, - "validFileImport": { - "message": "ನೀವು ಆಮದು ಮಾಡಲು ಮಾನ್ಯ ಫೈಲ್ ಅನ್ನು ಆಯ್ಕೆಮಾಡಬೇಕು." - }, "viewAccount": { "message": "ಖಾತೆಯನ್ನು ವೀಕ್ಷಿಸಿ" }, diff --git a/app/_locales/ko/messages.json b/app/_locales/ko/messages.json index c71e3c267..bd412c75c 100644 --- a/app/_locales/ko/messages.json +++ b/app/_locales/ko/messages.json @@ -1,33 +1,21 @@ { - "privacyModeDefault": { - "message": "이제 프라이버시 모드가 기본 설정으로 활성화됐습니다" - }, "chartOnlyAvailableEth": { "message": "이더리움 네트워크에서만 사용 가능한 차트." }, - "confirmClear": { - "message": "승인 된 웹 사이트를 삭제 하시겠습니까?" - }, "contractInteraction": { "message": "계약 상호 작용" }, - "clearApprovalData": { - "message": "승인 데이터 삭제" - }, "reject": { "message": "거부" }, - "providerRequest": { + "likeToConnect": { "message": "$1이 당신의 계정에 연결하길 원합니다." }, - "providerRequestInfo": { - "message": "아래 나열된 도메인은 Web3 API에 대한 액세스를 요청하여 Ethereum 블록 체인과 상호 작용할 수 있습니다. Ethereum 액세스를 승인하기 전에 항상 올바른 사이트에 있는지 다시 확인하십시오." - }, "about": { "message": "정보" }, "aboutSettingsDescription": { - "message": "버전, 지원 센터, 그리고 연락처 정보." + "message": "버전, 지원 센터, 그리고 연락처 정보" }, "acceleratingATransaction": { "message": "* 더 높은 가스 요금을 사용하여 트랜잭션을 가속화하면 네트워크에 의해 더 빨리 처리될 가능성이 증가하지만 항상 빠른 처리가 보장되는 것은 아닙니다." @@ -63,7 +51,7 @@ "message": "고급" }, "advancedSettingsDescription": { - "message": "개발자 기능 사용, 상태 로그 다운로드, 계정 재설정, 테스트넷 및 사용자 정의 RPC 설정." + "message": "개발자 기능 사용, 상태 로그 다운로드, 계정 재설정, 테스트넷 및 사용자 정의 RPC 설정" }, "advancedOptions": { "message": "고급 옵션" @@ -154,10 +142,6 @@ "balanceIsInsufficientGas": { "message": "현재 가스 총합에 대해 잔액이 부족합니다" }, - "betweenMinAndMax": { - "message": "$1 이상 $2 이하여야 합니다.", - "description": "helper for inputting hex as decimal input" - }, "blockExplorerUrl": { "message": "익스플로러 차단" }, @@ -249,9 +233,6 @@ "connect": { "message": "연결" }, - "connectRequest": { - "message": "연결 요청" - }, "connectingTo": { "message": "$1에 연결" }, @@ -291,9 +272,6 @@ "copiedExclamation": { "message": "복사됨!" }, - "copy": { - "message": "복사" - }, "copyAddress": { "message": "클립보드로 주소 복사" }, @@ -372,9 +350,6 @@ "directDepositEtherExplainer": { "message": "약간의 이더를 이미 보유하고 있다면, 새로 만든 지갑에 직접 입금하여 이더를 보유할 수 있습니다." }, - "dismiss": { - "message": "숨기기" - }, "done": { "message": "완료" }, @@ -540,10 +515,6 @@ "getStarted": { "message": "시작하기" }, - "greaterThanMin": { - "message": "$1 이상이어야 합니다.", - "description": "helper for inputting hex as decimal input" - }, "hardware": { "message": "하드웨어" }, @@ -659,10 +630,6 @@ "ledgerAccountRestriction": { "message": "새 계정을 추가하려면 최소 마지막 계정을 사용해야 합니다." }, - "lessThanMax": { - "message": "$1 이하여야합니다.", - "description": "helper for inputting hex as decimal input" - }, "letsGoSetUp": { "message": "네, 설정해볼게요!" }, @@ -861,9 +828,6 @@ "message": "개인키를 입력해주세요:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "시드 구문을 이곳에 붙여넣어 주세요!" - }, "pending": { "message": "펜딩 중" }, @@ -1177,9 +1141,6 @@ "storePhrase": { "message": "이 구문을 1Password같은 암호 관리자에 저장하세요." }, - "submit": { - "message": "제출" - }, "submitted": { "message": "제출됨" }, @@ -1347,9 +1308,6 @@ "userName": { "message": "사용자이름" }, - "validFileImport": { - "message": "가져오기 위해 유효한 파일을 선택해야 합니다." - }, "viewAccount": { "message": "계정 보기" }, diff --git a/app/_locales/lt/messages.json b/app/_locales/lt/messages.json index 7e029494f..c2a56ec54 100644 --- a/app/_locales/lt/messages.json +++ b/app/_locales/lt/messages.json @@ -1,33 +1,21 @@ { - "privacyModeDefault": { - "message": "Dabar privatumo režimas suaktyvintas pagal numatytąją nuostatą" - }, "chartOnlyAvailableEth": { "message": "Diagramos yra tik „Ethereum“ tinkluose." }, - "confirmClear": { - "message": "Ar tikrai norite panaikinti patvirtintas svetaines?" - }, "contractInteraction": { "message": "Sutartinė sąveika" }, - "clearApprovalData": { - "message": "Išvalyti asmeninius duomenis" - }, "reject": { "message": "Atmesti" }, - "providerRequest": { + "likeToConnect": { "message": "$1 norėtų prisijungti prie jūsų paskyros" }, - "providerRequestInfo": { - "message": "Ši svetainė prašo prieigos peržiūrėti jūsų dabartinės paskyros adresą. Visada patikrinkite, ar pasitikite svetainėmis, su kuriomis sąveikaujate." - }, "about": { "message": "Apie" }, "aboutSettingsDescription": { - "message": "Versija, palaikymo centras ir kontaktinė informacija." + "message": "Versija, palaikymo centras ir kontaktinė informacija" }, "acceleratingATransaction": { "message": "Operacijos paspartinimas naudojantis didesne dujų kaina padidina galimybes, kad ji bus greičiau apdorota tinkle, tačiau tai ne visada garantuojama. " @@ -63,7 +51,7 @@ "message": "Išplėstiniai" }, "advancedSettingsDescription": { - "message": "Prieigos kūrėjo funkcijos, būsenos žurnalų atsiuntimas, paskyros atstatymas, „testnet“ nustatymas ir pritaikytas RPC." + "message": "Prieigos kūrėjo funkcijos, būsenos žurnalų atsiuntimas, paskyros atstatymas, „testnet“ nustatymas ir pritaikytas RPC" }, "advancedOptions": { "message": "Išplėstinės parinktys" @@ -157,10 +145,6 @@ "basic": { "message": "Bendrieji" }, - "betweenMinAndMax": { - "message": "turi būti ne mažiau kaip $1 ir ne daugiau kaip $2.", - "description": "helper for inputting hex as decimal input" - }, "blockExplorerUrl": { "message": "Blokuoti naršyklę" }, @@ -252,9 +236,6 @@ "connect": { "message": "Prisijungti" }, - "connectRequest": { - "message": "Prijungimo užklausa" - }, "connectingTo": { "message": "Jungiamasi prie $1" }, @@ -294,9 +275,6 @@ "copiedExclamation": { "message": "Nukopijuota!" }, - "copy": { - "message": "Kopijuoti" - }, "copyAddress": { "message": "Kopijuoti adresą į iškarpinę" }, @@ -375,9 +353,6 @@ "directDepositEtherExplainer": { "message": "Jeigu jau turite šiek tiek eterių, sparčiausias būdas gauti eterių į naują piniginę yra tiesioginis įnašas." }, - "dismiss": { - "message": "Atsisakyti" - }, "done": { "message": "Atlikta" }, @@ -543,10 +518,6 @@ "getStarted": { "message": "Darbo pradžia" }, - "greaterThanMin": { - "message": "turi būti daugiau arba lygu $1.", - "description": "helper for inputting hex as decimal input" - }, "happyToSeeYou": { "message": "Džiaugiamės jus matydami." }, @@ -665,10 +636,6 @@ "ledgerAccountRestriction": { "message": "Prieš įtraukdami naują, turite pasinaudoti paskutine paskyra." }, - "lessThanMax": { - "message": "turi būti mažiau arba lygu $1.", - "description": "helper for inputting hex as decimal input" - }, "letsGoSetUp": { "message": "Taip, pradėkime!" }, @@ -870,9 +837,6 @@ "message": "Čia įklijuokite asmeninio rakto eilutę:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "Čia įklijuokite savo atkūrimo frazę!" - }, "pending": { "message": "laukiama patvirtinimo" }, @@ -1186,9 +1150,6 @@ "storePhrase": { "message": "Laikykite šią frazę slaptažodžių tvarkyklėje kaip 1 slaptažodį." }, - "submit": { - "message": "Pateikti" - }, "submitted": { "message": "Pateikta" }, @@ -1356,9 +1317,6 @@ "userName": { "message": "Vartotojo vardas" }, - "validFileImport": { - "message": "Turite pasirinkti galiojantį failą, kurį pageidaujate importuoti." - }, "viewAccount": { "message": "Žiūrėti paskyrą" }, diff --git a/app/_locales/lv/messages.json b/app/_locales/lv/messages.json index ace47600f..0b5af9c4e 100644 --- a/app/_locales/lv/messages.json +++ b/app/_locales/lv/messages.json @@ -1,19 +1,10 @@ { - "privacyModeDefault": { - "message": "Privātais režīms tagad ieslēgts pēc noklusējuma" - }, "chartOnlyAvailableEth": { "message": "Grafiks pieejams vienīgi Ethereum tīklos." }, - "confirmClear": { - "message": "Vai tiešām vēlaties dzēst apstiprinātās vietnes?" - }, "contractInteraction": { "message": "Līguma mijiedarbības" }, - "clearApprovalData": { - "message": "Notīrīt konfidencialitātes datus" - }, "appName": { "message": "MetaMask", "description": "The name of the application" @@ -21,12 +12,9 @@ "reject": { "message": "Noraidīt" }, - "providerRequest": { + "likeToConnect": { "message": "$1 vēlas izveidot savienojumu ar jūsu kontu" }, - "providerRequestInfo": { - "message": "Šī lapa pieprasa piekļuvi jūsu pašreizēja konta adreses informācijai. Vienmēr pārliecinieties, ka uzticaties lapām, kuras apmeklējat." - }, "about": { "message": "Par" }, @@ -67,7 +55,7 @@ "message": "Papildu" }, "advancedSettingsDescription": { - "message": "Piekļūstiet izstrādātāju funkcijām, lejupielādējiet stāvokļu žurnālus, atiestatiet kontu, iestatiet testa tīklus un pielāgotos RPC izsaukumus." + "message": "Piekļūstiet izstrādātāju funkcijām, lejupielādējiet stāvokļu žurnālus, atiestatiet kontu, iestatiet testa tīklus un pielāgotos RPC izsaukumus" }, "advancedOptions": { "message": "Papildu opcijas" @@ -157,10 +145,6 @@ "basic": { "message": "Pamata" }, - "betweenMinAndMax": { - "message": "jābūt lielākai par vai vienādai ar $1 un mazākai par vai vienādai ar $2.", - "description": "helper for inputting hex as decimal input" - }, "blockExplorerUrl": { "message": "Bloķēt Explorer" }, @@ -252,9 +236,6 @@ "connect": { "message": "Pievienošana" }, - "connectRequest": { - "message": "Savienojuma pieprasījums" - }, "connectingTo": { "message": "Pieslēdzas $1" }, @@ -294,9 +275,6 @@ "copiedExclamation": { "message": "Nokopēts!" }, - "copy": { - "message": "Kopēt" - }, "copyAddress": { "message": "Iekopēt adresi starpliktuvē" }, @@ -375,9 +353,6 @@ "directDepositEtherExplainer": { "message": "Ja jums jau ir Ether, tad visātrāk Ether savā makā varat saņemt ar tiešo iemaksu." }, - "dismiss": { - "message": "Noraidīt" - }, "done": { "message": "Pabeigts" }, @@ -539,10 +514,6 @@ "getStarted": { "message": "Sākt darbu" }, - "greaterThanMin": { - "message": "jābūt lielākam par vai vienādam ar $1.", - "description": "helper for inputting hex as decimal input" - }, "happyToSeeYou": { "message": "Priecājamies jūs redzēt!" }, @@ -661,10 +632,6 @@ "ledgerAccountRestriction": { "message": "Jums jāizmanto pēdējais konts pirms varat pievienot jaunu." }, - "lessThanMax": { - "message": "jābūt mazākam par vai vienādam ar $1.", - "description": "helper for inputting hex as decimal input" - }, "letsGoSetUp": { "message": "Jā, sāksim iestatīšanu!" }, @@ -866,9 +833,6 @@ "message": "Ielīmējiet privātās atslēgas rindu šeit:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "Iekopējiet savu atkopšanas frāzi šeit!" - }, "pending": { "message": "gaida" }, @@ -1182,9 +1146,6 @@ "storePhrase": { "message": "Saglabājiet šo frāzi paroļu pārvaldniekā, piemēram, 1Password." }, - "submit": { - "message": "Iesniegt" - }, "submitted": { "message": "Iesniegts" }, @@ -1352,9 +1313,6 @@ "userName": { "message": "Lietotājvārds" }, - "validFileImport": { - "message": "Importēšanai jāatlasa derīga datne." - }, "viewAccount": { "message": "Skatīt kontu" }, diff --git a/app/_locales/ml/messages.json b/app/_locales/ml/messages.json index cbcee4186..c898d1dc2 100644 --- a/app/_locales/ml/messages.json +++ b/app/_locales/ml/messages.json @@ -42,9 +42,6 @@ "connect": { "message": "കണക്‌റ്റുചെയ്യുക" }, - "copy": { - "message": "പകര്‍ത്തുക" - }, "copyToClipboard": { "message": "ക്ലിപ്പ്ബോർഡിലേക്ക് പകർത്തുക" }, @@ -57,9 +54,6 @@ "details": { "message": "വിശദാംശങ്ങൾ‌" }, - "dismiss": { - "message": "ബഹിഷ്‌ക്കരിക്കുക" - }, "done": { "message": "പൂർത്തിയാക്കി" }, @@ -131,9 +125,6 @@ "settings": { "message": "ക്രമീകരണങ്ങള്‍" }, - "submit": { - "message": "സമര്‍പ്പിക്കൂ" - }, "tryAgain": { "message": "വീണ്ടും ശ്രമിക്കുക" }, diff --git a/app/_locales/mr/messages.json b/app/_locales/mr/messages.json index af06c03fb..ef341f40c 100644 --- a/app/_locales/mr/messages.json +++ b/app/_locales/mr/messages.json @@ -42,9 +42,6 @@ "connect": { "message": "कनेक्‍ट करा" }, - "copy": { - "message": "कॉपी करा" - }, "copyToClipboard": { "message": "क्लिपबोर्डवर कॉपी करा" }, @@ -57,9 +54,6 @@ "details": { "message": "तपशील" }, - "dismiss": { - "message": "डिसमिस करा" - }, "done": { "message": "पूर्ण झाले" }, @@ -131,9 +125,6 @@ "settings": { "message": "सेटिंग्ज" }, - "submit": { - "message": "सबमिट करा" - }, "tryAgain": { "message": "पुन्हा प्रयत्न करा" }, diff --git a/app/_locales/ms/messages.json b/app/_locales/ms/messages.json index 03f9ac0b2..7c591b606 100644 --- a/app/_locales/ms/messages.json +++ b/app/_locales/ms/messages.json @@ -1,33 +1,21 @@ { - "privacyModeDefault": { - "message": "Mod Privasi kini diaktifkan secara lalai" - }, "chartOnlyAvailableEth": { "message": "Carta hanya tersedia di rangkaian Ethereum." }, - "confirmClear": { - "message": "Adakah anda pasti mahu mengosongkan tapak web diluluskan?" - }, "contractInteraction": { "message": "Interaksi Kontrak" }, - "clearApprovalData": { - "message": "Kosongkan Data Privasi" - }, "reject": { "message": "Tolak" }, - "providerRequest": { + "likeToConnect": { "message": "$1 ingin menyambung kepada akaun anda" }, - "providerRequestInfo": { - "message": "Tapak ini meminta akses untuk melihat alamat akaun semasa anda. Sentiasa pastikan anda mempercayai tapak web yang anda berinteraksi." - }, "about": { "message": "Mengenai" }, "aboutSettingsDescription": { - "message": "Versi, pusat sokongan, dan maklumat perhubungan." + "message": "Versi, pusat sokongan, dan maklumat perhubungan" }, "acceleratingATransaction": { "message": "* Mempercepatkan transaksi menggunakan harga gas lebih tinggi akan meningkatkan peluang diproses oleh rangkaian lebih cepat, tetapi ini pun tidak sentiasa dijamin." @@ -63,7 +51,7 @@ "message": "Lanjutan" }, "advancedSettingsDescription": { - "message": "Akses ciri-ciri pembangun, muat turun Log Keadaan, Set Semula Akaun, sediakan jaringan ujian dan RPC tersuai." + "message": "Akses ciri-ciri pembangun, muat turun Log Keadaan, Set Semula Akaun, sediakan jaringan ujian dan RPC tersuai" }, "advancedOptions": { "message": "Pilihan Lanjutan" @@ -157,10 +145,6 @@ "basic": { "message": "Asas" }, - "betweenMinAndMax": { - "message": "mestilah lebih besar atau sama dengan $1 dan kurang atau sama dengan $2.", - "description": "helper for inputting hex as decimal input" - }, "blockExplorerUrl": { "message": "Sekat Explorer" }, @@ -249,9 +233,6 @@ "connect": { "message": "Sambung" }, - "connectRequest": { - "message": "Sambungkan Permintaan" - }, "connectingTo": { "message": "Menyambungkan kepada $1" }, @@ -291,9 +272,6 @@ "copiedExclamation": { "message": "Disalin!" }, - "copy": { - "message": "Salin" - }, "copyAddress": { "message": "Salin alamat kepada papan klip" }, @@ -366,9 +344,6 @@ "directDepositEtherExplainer": { "message": "Jika anda sudah mempunyai Ether, cara paling cepat untuk mendapatkan Ether di dompet baru anda ialah dengan deposit langsung." }, - "dismiss": { - "message": "Singkirkan" - }, "done": { "message": "Selesai" }, @@ -527,10 +502,6 @@ "getStarted": { "message": "Bermula" }, - "greaterThanMin": { - "message": "mestilah melebihi atau bersamaan dengan $1.", - "description": "helper for inputting hex as decimal input" - }, "happyToSeeYou": { "message": "Kami gembira bertemu anda." }, @@ -645,10 +616,6 @@ "ledgerAccountRestriction": { "message": "Anda perlu menggunakan akaun terakhir anda sebelum anda boleh menambah yang baru." }, - "lessThanMax": { - "message": "mestilah kurang atau bersamaan dengan $1.", - "description": "helper for inputting hex as decimal input" - }, "letsGoSetUp": { "message": "Ya, mari sediakannya!" }, @@ -841,9 +808,6 @@ "message": "Tampal rentetan kekunci persendirian anda di sini:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "Tampal ungkapan benih anda di sini!" - }, "pending": { "message": "menunggu" }, @@ -1157,9 +1121,6 @@ "storePhrase": { "message": "Simpan ungkapan ini di dalam pengurus kata laluan seperti 1Password." }, - "submit": { - "message": "Serah" - }, "submitted": { "message": "Dihantar" }, @@ -1324,9 +1285,6 @@ "userName": { "message": "Nama pengguna" }, - "validFileImport": { - "message": "Anda mesti pilih fail yang sah untuk diimport." - }, "viewAccount": { "message": "Paparkan Akaun" }, diff --git a/app/_locales/nl/messages.json b/app/_locales/nl/messages.json index ba83e37a1..7ba285208 100644 --- a/app/_locales/nl/messages.json +++ b/app/_locales/nl/messages.json @@ -1,16 +1,7 @@ { - "confirmClear": { - "message": "Weet je zeker dat je goedgekeurde websites wilt wissen?" - }, - "clearApprovalData": { - "message": "Gegevens over goedkeuring wissen" - }, "reject": { "message": "Afwijzen" }, - "providerRequestInfo": { - "message": "Het onderstaande domein probeert toegang tot de Ethereum API te vragen zodat deze kan communiceren met de Ethereum-blockchain. Controleer altijd eerst of u op de juiste site bent voordat u Ethereum-toegang goedkeurt." - }, "accountDetails": { "message": "Accountgegevens" }, @@ -52,10 +43,6 @@ "balanceIsInsufficientGas": { "message": "Onvoldoende saldo voor huidig ​​gastotaal" }, - "betweenMinAndMax": { - "message": "moet groter zijn dan of gelijk zijn aan $1 en kleiner dan of gelijk aan $2.", - "description": "helper for inputting hex as decimal input" - }, "blockiesIdenticon": { "message": "Gebruik Blockies Identicon" }, @@ -92,9 +79,6 @@ "copiedExclamation": { "message": "Gekopieerde!" }, - "copy": { - "message": "Kopiëren" - }, "copyToClipboard": { "message": "Kopieer naar klembord" }, @@ -197,10 +181,6 @@ "message": "Haal Ether uit een kraan voor de $1", "description": "Displays network name for Ether faucet" }, - "greaterThanMin": { - "message": "moet groter zijn dan of gelijk zijn aan $1.", - "description": "helper for inputting hex as decimal input" - }, "here": { "message": "hier", "description": "as in -click here- for more information (goes with troubleTokenBalances)" @@ -259,10 +239,6 @@ "kovan": { "message": "Kovan-testnetwerk" }, - "lessThanMax": { - "message": "moet kleiner zijn dan of gelijk zijn aan $1.", - "description": "helper for inputting hex as decimal input" - }, "likeToAddTokens": { "message": "Wil je deze tokens toevoegen?" }, @@ -331,9 +307,6 @@ "message": "Plak hier uw privésleutelstring:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "Plak je back-up woorden hier!" - }, "personalAddressDetected": { "message": "Persoonlijk adres gedetecteerd. Voer het tokencontractadres in." }, @@ -437,9 +410,6 @@ "stateLogsDescription": { "message": "Staatslogboeken bevatten uw openbare accountadressen en verzonden transacties." }, - "submit": { - "message": "voorleggen" - }, "supportCenter": { "message": "Bezoek ons ​​ondersteuningscentrum" }, @@ -484,9 +454,6 @@ "usedByClients": { "message": "Gebruikt door verschillende klanten" }, - "validFileImport": { - "message": "U moet een geldig bestand selecteren om te importeren." - }, "viewAccount": { "message": "Bekijk account" }, diff --git a/app/_locales/no/messages.json b/app/_locales/no/messages.json index 83a6f09d3..6617284b7 100644 --- a/app/_locales/no/messages.json +++ b/app/_locales/no/messages.json @@ -1,33 +1,21 @@ { - "privacyModeDefault": { - "message": "Personvernmodus er nå aktivert som standard" - }, "chartOnlyAvailableEth": { "message": "Diagram kun tilgjengelig på Ethereum-nettverk." }, - "confirmClear": { - "message": "Er du sikker på at du vil tømme godkjente nettsteder?" - }, "contractInteraction": { "message": "Kontraktssamhandling" }, - "clearApprovalData": { - "message": "Tøm personvernsdata" - }, "reject": { "message": "Avslå" }, - "providerRequest": { + "likeToConnect": { "message": "$1 ønsker å forbindes med kontoen din " }, - "providerRequestInfo": { - "message": "Dette nettstedet ber om tilgang til å vise din nåværende kontoadresse. Alltid kontroller at du stoler på nettstedene du samhandler med." - }, "about": { "message": "Info" }, "aboutSettingsDescription": { - "message": "Versjon, brukerstøtte og kontaktinformasjon." + "message": "Versjon, brukerstøtte og kontaktinformasjon" }, "acceleratingATransaction": { "message": "* Akselerering av en transaksjon ved å bruke en høyere datakraftspris øker sjansene for å bli behandlet av nettverket raskere, men det er ikke alltid garantert." @@ -63,7 +51,7 @@ "message": "Avansert" }, "advancedSettingsDescription": { - "message": "Få tilgang til utviklerfunksjoner, last ned tilstandslogger, tilbakestill konto, installer testnett og tilpasset RPC." + "message": "Få tilgang til utviklerfunksjoner, last ned tilstandslogger, tilbakestill konto, installer testnett og tilpasset RPC" }, "advancedOptions": { "message": "Avanserte valg" @@ -157,10 +145,6 @@ "basic": { "message": "Enkle" }, - "betweenMinAndMax": { - "message": "må være større enn eller det samme som $1 og mindre enn eller det samme som $2. ", - "description": "helper for inputting hex as decimal input" - }, "blockExplorerUrl": { "message": "Blokkér Explorer" }, @@ -249,9 +233,6 @@ "connect": { "message": "Koble til" }, - "connectRequest": { - "message": "Kontaktforespørsel " - }, "connectingTo": { "message": "Forbinder til $1 " }, @@ -291,9 +272,6 @@ "copiedExclamation": { "message": "Kopiert!" }, - "copy": { - "message": "Kopier" - }, "copyAddress": { "message": "Kopier adresse til utklippstavlen " }, @@ -372,9 +350,6 @@ "directDepositEtherExplainer": { "message": "Hvis du allerede har noe Ether, er den raskeste måten å få Ether i den nye lommeboken din på ved hjelp av direkte innskudd." }, - "dismiss": { - "message": "Lukk" - }, "done": { "message": "Ferdig" }, @@ -536,10 +511,6 @@ "getStarted": { "message": "Kom i gang" }, - "greaterThanMin": { - "message": "Må være mer enn eller tilsvarende $1.", - "description": "helper for inputting hex as decimal input" - }, "happyToSeeYou": { "message": "Vi er glade for å se deg " }, @@ -655,10 +626,6 @@ "ledgerAccountRestriction": { "message": "Du må bruke den siste kontoen din før du kan legge til en ny." }, - "lessThanMax": { - "message": "Må være mindre eller likt som $1.", - "description": "helper for inputting hex as decimal input" - }, "letsGoSetUp": { "message": "Ja, la oss komme i gang!" }, @@ -860,9 +827,6 @@ "message": "Lim inn din private nøkkelstreng her:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "Lim inn seed-frasen din her!" - }, "pending": { "message": "i påvente " }, @@ -1164,9 +1128,6 @@ "storePhrase": { "message": "Lagre denne frasen i en passordbehandler slik som 1Password." }, - "submit": { - "message": "Send" - }, "submitted": { "message": "Sendt inn" }, @@ -1328,9 +1289,6 @@ "userName": { "message": "Brukernavn" }, - "validFileImport": { - "message": "Du må velge en gyldig fil å importere" - }, "viewAccount": { "message": "Se konto" }, diff --git a/app/_locales/ph/messages.json b/app/_locales/ph/messages.json index a3df5d4ed..1a3807ad5 100644 --- a/app/_locales/ph/messages.json +++ b/app/_locales/ph/messages.json @@ -1,10 +1,4 @@ { - "confirmClear": { - "message": "Sigurado ka bang gusto mong i-clear ang mga naaprubahang website?" - }, - "clearApprovalData": { - "message": "Tanggalin ang data ng pag-apruba" - }, "appName": { "message": "MetaMask", "description": "The name of the application" @@ -15,9 +9,6 @@ "reject": { "message": "Tanggihan" }, - "providerRequestInfo": { - "message": "Ang domain na nakalista sa ibaba ay sinusubukang humiling ng access sa Ethereum API upang maaari itong makipag-ugnayan sa Ethereum blockchain. Laging i-double check na ikaw ay nasa tamang site bago aprubahan ang Ethereum access." - }, "accountDetails": { "message": "Detalye ng Account" }, @@ -46,10 +37,6 @@ "balanceIsInsufficientGas": { "message": "Kulang ang balanse para sa kasalukuyang gas total" }, - "betweenMinAndMax": { - "message": "dapat mas malaki o katumbas ng $1 at mas mababa o katumbas ng $2.", - "description": "helper para sa pag-input ng hex bilang decimal input" - }, "buyCoinSwitch": { "message": "Bumili sa CoinSwitch" }, @@ -80,9 +67,6 @@ "copiedExclamation": { "message": "Kinopya!" }, - "copy": { - "message": "Kinopya" - }, "copyToClipboard": { "message": "Kinopya sa clipboard" }, @@ -167,10 +151,6 @@ "message": "Kumuha ng Ether mula sa faucet para sa $1", "description": "Ipinapakita ang pangalan ng network para sa Ether faucet" }, - "greaterThanMin": { - "message": "dapat mas malaki o katumbas ng $1.", - "description": "helper para sa pag-input ng hex bilang decimal input" - }, "here": { "message": "i-click ito", "description": "tulad ng -i-click dito- para sa mas maraming impormasyon (kasama ng troubleTokenBalances)" @@ -204,10 +184,6 @@ "invalidInput": { "message": "Invalid ang input." }, - "lessThanMax": { - "message": "dapat mas mababa o katumbas ng $1.", - "description": "helper para sa pag-input ng hex bilang decimal input" - }, "loading": { "message": "Naglo-load..." }, @@ -252,9 +228,6 @@ "message": "I-paste dito ang iyong private key string:", "description": "Para sa pag-import ng account mula sa private key" }, - "pasteSeed": { - "message": "I-paste dito ang iyong seed phrase!" - }, "privateKeyWarning": { "message": "Babala: Huwag sabihin sa kahit na sino ang key na ito. Maaring makuha at manakaw ng sinumang nakakaalam ng iyong private key ang mga assets sa iyong account." }, @@ -303,9 +276,6 @@ "sigRequest": { "message": "Hiling na Signature" }, - "submit": { - "message": "I-submit" - }, "toETHviaShapeShift": { "message": "$1 sa ETH sa pamamagitan ng ShapeShift", "description": "Pupunan ng system ang deposit type sa simula ng mensahe" diff --git a/app/_locales/pl/messages.json b/app/_locales/pl/messages.json index 5ffd5585a..2441408f6 100644 --- a/app/_locales/pl/messages.json +++ b/app/_locales/pl/messages.json @@ -1,33 +1,21 @@ { - "privacyModeDefault": { - "message": "Tryb prywatny jest domyślnie włączony" - }, "chartOnlyAvailableEth": { "message": "Wykres dostępny tylko w sieciach Ethereum" }, - "confirmClear": { - "message": "Czy na pewno chcesz usunąć zatwierdzone strony internetowe?" - }, "contractInteraction": { "message": "Interakcja z kontraktem" }, - "clearApprovalData": { - "message": "Usuń dane poufne" - }, "reject": { "message": "Odrzuć" }, - "providerRequest": { + "likeToConnect": { "message": "$1 chce połączyć się z Twoim kontem" }, - "providerRequestInfo": { - "message": "Ta strona prosi o dostęp, aby zobaczyć adres Twojego aktualnego konta. Zawsze upewnij się, że ufasz stronom, z którymi wchodzisz w interakcję." - }, "about": { "message": "Informacje" }, "aboutSettingsDescription": { - "message": "Wersja, centrum wsparcia i dane kontaktowe." + "message": "Wersja, centrum wsparcia i dane kontaktowe" }, "acceleratingATransaction": { "message": "* Przyspieszenie transakcji poprzez zastosowanie wyższej ceny gazu zwiększa szanse na jej szybsze przetworzenie przez sieć, jednak skuteczność tej operacji nie jest gwarantowana." @@ -63,7 +51,7 @@ "message": "Zaawansowane" }, "advancedSettingsDescription": { - "message": "Dostęp do funkcji programisty, pobieranie dzienników stanu, resetowanie konta, konfigurowanie sieci testowych i niestandardowe RPC." + "message": "Dostęp do funkcji programisty, pobieranie dzienników stanu, resetowanie konta, konfigurowanie sieci testowych i niestandardowe RPC" }, "advancedOptions": { "message": "Opcje zaawansowane" @@ -157,10 +145,6 @@ "basic": { "message": "Podstawy" }, - "betweenMinAndMax": { - "message": "musi być większe lub równe $1 i mniejsze lub równe $2,", - "description": "helper for inputting hex as decimal input" - }, "blockExplorerUrl": { "message": "Przeglądaj blok" }, @@ -249,9 +233,6 @@ "connect": { "message": "Połącz" }, - "connectRequest": { - "message": "Potwierdź żądanie" - }, "connectingTo": { "message": "Łączenie z $1" }, @@ -291,9 +272,6 @@ "copiedExclamation": { "message": "Skopiowane!" }, - "copy": { - "message": "Skopiuj" - }, "copyAddress": { "message": "Skopiuj adres do schowka" }, @@ -372,9 +350,6 @@ "directDepositEtherExplainer": { "message": "Jeśli już masz Eter, najszybciej umieścisz go w swoim nowym portfelu przy pomocy bezpośredniego depozytu." }, - "dismiss": { - "message": "Zamknij" - }, "done": { "message": "Gotowe" }, @@ -540,10 +515,6 @@ "getStarted": { "message": "Rozpocznij" }, - "greaterThanMin": { - "message": "musi być większe lub równe $1.", - "description": "helper for inputting hex as decimal input" - }, "happyToSeeYou": { "message": "Cieszymy się, że tu jesteś." }, @@ -662,10 +633,6 @@ "ledgerAccountRestriction": { "message": "Musisz użyć swojego poprzedniego konta zanim dodasz kolejne." }, - "lessThanMax": { - "message": "musi być mniejsze lub równe $1.", - "description": "helper for inputting hex as decimal input" - }, "letsGoSetUp": { "message": "Tak, zacznijmy od początku!" }, @@ -861,9 +828,6 @@ "message": "Tutaj wklej swój prywatny klucz:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "Tutaj wklej swoją frazę seed!" - }, "pending": { "message": "oczekiwanie" }, @@ -1177,9 +1141,6 @@ "storePhrase": { "message": "Przechowuj tę frazę w menedżerze haseł, takim jak 1Password." }, - "submit": { - "message": "Wyślij" - }, "submitted": { "message": "Wysłane" }, @@ -1341,9 +1302,6 @@ "userName": { "message": "Nazwa użytkownika" }, - "validFileImport": { - "message": "Należy wybrać prawidłowy plik do zaimportowania." - }, "viewAccount": { "message": "Zobacz konto" }, diff --git a/app/_locales/pt/messages.json b/app/_locales/pt/messages.json index 70c2e52ae..9d9ed0450 100644 --- a/app/_locales/pt/messages.json +++ b/app/_locales/pt/messages.json @@ -1,16 +1,7 @@ { - "confirmClear": { - "message": "Tem certeza de que deseja limpar sites aprovados?" - }, - "clearApprovalData": { - "message": "Limpar dados de aprovação" - }, "reject": { "message": "Rejeitar" }, - "providerRequestInfo": { - "message": "O domínio listado abaixo está tentando solicitar acesso à API Ethereum para que ele possa interagir com o blockchain Ethereum. Sempre verifique se você está no site correto antes de aprovar o acesso à Ethereum." - }, "account": { "message": "Conta" }, @@ -55,10 +46,6 @@ "balanceIsInsufficientGas": { "message": "Saldo insuficiente para a quantidade de gas total" }, - "betweenMinAndMax": { - "message": "tem de ser maior ou igual a $1 e menor ou igual a $2.", - "description": "helper for inputting hex as decimal input" - }, "blockiesIdenticon": { "message": "Usar Blockies Identicon" }, @@ -95,9 +82,6 @@ "copiedExclamation": { "message": "Copiado!" }, - "copy": { - "message": "Copiar" - }, "copyToClipboard": { "message": "Copiar para o clipboard" }, @@ -203,10 +187,6 @@ "message": "Obter Ether de um faucet por $1", "description": "Displays network name for Ether faucet" }, - "greaterThanMin": { - "message": "tem de ser maior ou igual a $1.", - "description": "helper for inputting hex as decimal input" - }, "here": { "message": "aqui", "description": "as in -click here- for more information (goes with troubleTokenBalances)" @@ -265,10 +245,6 @@ "kovan": { "message": "Rede de Teste Kovan" }, - "lessThanMax": { - "message": "tem de ser menor ou igual a $1.", - "description": "helper for inputting hex as decimal input" - }, "likeToAddTokens": { "message": "Gostaria de adicionar estes tokens?" }, @@ -341,9 +317,6 @@ "message": "Cole aqui a sua chave privada:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "Cole aqui a sua frase seed!" - }, "personalAddressDetected": { "message": "Endereço pessoal detectado. Introduza o endereço do contrato do token." }, @@ -447,9 +420,6 @@ "stateLogsDescription": { "message": "Registo de estado podem conter o seu endereço e transações enviadas da sua conta pública." }, - "submit": { - "message": "Submeter" - }, "supportCenter": { "message": "Visitar o nosso Centro de Suporte" }, @@ -494,9 +464,6 @@ "usedByClients": { "message": "Utilizado por vários tipos de clientes" }, - "validFileImport": { - "message": "Deve selecionar um ficheiro válido para importar." - }, "viewAccount": { "message": "Ver Conta" }, diff --git a/app/_locales/pt_BR/messages.json b/app/_locales/pt_BR/messages.json index ecb223dcb..e2de06a19 100644 --- a/app/_locales/pt_BR/messages.json +++ b/app/_locales/pt_BR/messages.json @@ -1,19 +1,10 @@ { - "privacyModeDefault": { - "message": "O Modo de Privacidade está ativado por padrão" - }, "chartOnlyAvailableEth": { "message": "Tabela disponível apenas em redes de Ethereum." }, - "confirmClear": { - "message": "Tem certeza de que deseja limpar os sites aprovados?" - }, "contractInteraction": { "message": "Interação do Contrato" }, - "clearApprovalData": { - "message": "Limpar Dados de Privacidade" - }, "appName": { "message": "MetaMask", "description": "The name of the application" @@ -21,17 +12,14 @@ "reject": { "message": "Rejeitar" }, - "providerRequest": { + "likeToConnect": { "message": "$1 gostaria de se conectar à sua conta" }, - "providerRequestInfo": { - "message": "Este site está solicitando acesso para visualizar o seu endereço de conta atual. Certifique-se sempre de confiar nos sites com os quais você interage." - }, "about": { "message": "Sobre" }, "aboutSettingsDescription": { - "message": "Versão, centro de apoio e informações de contato." + "message": "Versão, centro de apoio e informações de contato" }, "acceleratingATransaction": { "message": "* Acelerar uma transação usando um preço de gás mais alto aumenta suas chances de a rede processá-la de forma mais rápida, mas isso nem sempre é garantido." @@ -67,7 +55,7 @@ "message": "Avançado" }, "advancedSettingsDescription": { - "message": "Acesse recursos do desenvolvedor, baixe registros de estado, redefina a conta, configure testnets e personalize RPC." + "message": "Acesse recursos do desenvolvedor, baixe registros de estado, redefina a conta, configure testnets e personalize RPC" }, "advancedOptions": { "message": "Opções avançadas" @@ -157,10 +145,6 @@ "basic": { "message": "Básicas" }, - "betweenMinAndMax": { - "message": "deve ser maior ou igual a $1 e menor ou igual a $2.", - "description": "helper for inputting hex as decimal input" - }, "blockExplorerView": { "message": "Ver conta em $1", "description": "$1 replaced by URL for custom block explorer" @@ -246,9 +230,6 @@ "connect": { "message": "Conectar-se" }, - "connectRequest": { - "message": "Solicitação de Conexão" - }, "connectingTo": { "message": "Conectando a $1" }, @@ -288,9 +269,6 @@ "copiedExclamation": { "message": "Copiado!" }, - "copy": { - "message": "Copiar" - }, "copyAddress": { "message": "Copiar endereço para a área de transferência" }, @@ -369,9 +347,6 @@ "directDepositEtherExplainer": { "message": "Se você já tem Ether, a forma mais rápida de colocá-lo em sua nova carteira é o depósito direto." }, - "dismiss": { - "message": "Dispensar" - }, "done": { "message": "Concluído" }, @@ -537,10 +512,6 @@ "getStarted": { "message": "Primeiros passos" }, - "greaterThanMin": { - "message": "deve ser maior ou igual a $1.", - "description": "helper for inputting hex as decimal input" - }, "happyToSeeYou": { "message": "Estamos felizes em vê-lo." }, @@ -659,10 +630,6 @@ "ledgerAccountRestriction": { "message": "Você precisa usar sua última conta antes de adicionar uma nova." }, - "lessThanMax": { - "message": "deve ser inferior ou igual a $1.", - "description": "helper for inputting hex as decimal input" - }, "letsGoSetUp": { "message": "Sim, vamos configurar!" }, @@ -855,9 +822,6 @@ "message": "Cole a string de sua chave particular aqui:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "Cole sua seed phrase aqui!" - }, "pending": { "message": "pendente" }, @@ -1171,9 +1135,6 @@ "storePhrase": { "message": "Guarde esta frase em um gerenciador de senhas como o 1Password." }, - "submit": { - "message": "Enviar" - }, "submitted": { "message": "Enviado" }, @@ -1335,9 +1296,6 @@ "userName": { "message": "Nome de usuário" }, - "validFileImport": { - "message": "Você precisa selecionar um arquivo válido para importar." - }, "viewAccount": { "message": "Visualizar conta" }, diff --git a/app/_locales/pt_PT/messages.json b/app/_locales/pt_PT/messages.json index ce35721de..ae85eb26c 100644 --- a/app/_locales/pt_PT/messages.json +++ b/app/_locales/pt_PT/messages.json @@ -48,9 +48,6 @@ "copiedExclamation": { "message": "Copiado!" }, - "copy": { - "message": "Copiar" - }, "copyToClipboard": { "message": "Copiar para a área de transferência" }, @@ -66,9 +63,6 @@ "details": { "message": "Detalhes" }, - "dismiss": { - "message": "Ignorar" - }, "done": { "message": "Concluído" }, @@ -156,9 +150,6 @@ "settings": { "message": "Definições" }, - "submit": { - "message": "Submeter" - }, "tips": { "message": "Doações" }, diff --git a/app/_locales/ro/messages.json b/app/_locales/ro/messages.json index 5fed700da..3ed11cb86 100644 --- a/app/_locales/ro/messages.json +++ b/app/_locales/ro/messages.json @@ -1,33 +1,21 @@ { - "privacyModeDefault": { - "message": "Modul de confidențialitate este activat acum în mod implicit" - }, "chartOnlyAvailableEth": { "message": "Grafic disponibil numai pe rețelele Ethereum." }, - "confirmClear": { - "message": "Sunteți sigur că doriți să ștergeți site-urile aprobate?" - }, "contractInteraction": { "message": "Interacțiune contract" }, - "clearApprovalData": { - "message": "Ștergeți datele confidențiale" - }, "reject": { "message": "Respingeți" }, - "providerRequest": { + "likeToConnect": { "message": "$1 ar dori să se conecteze la contul dvs." }, - "providerRequestInfo": { - "message": "Acest site solicită acces pentru a vedea adresa curentă a contului dvs. Asigurați-vă întotdeauna că aveți încredere în site-urile cu care interacționați." - }, "about": { "message": "Despre" }, "aboutSettingsDescription": { - "message": "Versiune, centru de asistență și date de contact." + "message": "Versiune, centru de asistență și date de contact" }, "acceleratingATransaction": { "message": "* Accelerarea unei tranzacții folosind un preț în gas mai mare îi crește șansele de a fi procesată mai rapid de rețea, însă acest lucru nu este garantat întotdeauna." @@ -63,7 +51,7 @@ "message": "Avansate" }, "advancedSettingsDescription": { - "message": "Accesați funcții pentru dezvoltatori, descărcați Jurnale de stare, resetați contul, configurați rețele de test și RPC personalizat." + "message": "Accesați funcții pentru dezvoltatori, descărcați Jurnale de stare, resetați contul, configurați rețele de test și RPC personalizat" }, "advancedOptions": { "message": "Opțiuni avansate" @@ -157,10 +145,6 @@ "basic": { "message": "De bază" }, - "betweenMinAndMax": { - "message": "trebuie să fie mai mare sau egal cu $1 și mai mic sau egal cu $2.", - "description": "helper for inputting hex as decimal input" - }, "blockExplorerUrl": { "message": "Explorator blocuri" }, @@ -252,9 +236,6 @@ "connect": { "message": "Conectează-te" }, - "connectRequest": { - "message": "Solicitare de conectare" - }, "connectingTo": { "message": "Se conectează la $1" }, @@ -294,9 +275,6 @@ "copiedExclamation": { "message": "Copiat!" }, - "copy": { - "message": "Copiază" - }, "copyAddress": { "message": "Copiere adresă în clipboard" }, @@ -375,9 +353,6 @@ "directDepositEtherExplainer": { "message": "Dacă deja aveți Ether, cel mai rapid mod de a avea Ether în portofelul nou prin depunere directă." }, - "dismiss": { - "message": "Închide" - }, "done": { "message": "Efectuat" }, @@ -536,10 +511,6 @@ "getStarted": { "message": "Începe" }, - "greaterThanMin": { - "message": "trebuie să fie mai mare sau egal cu $1.", - "description": "helper for inputting hex as decimal input" - }, "happyToSeeYou": { "message": "Ne pare bine să vă vedem." }, @@ -655,10 +626,6 @@ "ledgerAccountRestriction": { "message": "Trebuie să folosiți ultimul cont înainte să adăugați altul." }, - "lessThanMax": { - "message": "trebuie să fie mai mic sau egal cu $1.", - "description": "helper for inputting hex as decimal input" - }, "letsGoSetUp": { "message": "Da, hai să configurăm!" }, @@ -857,9 +824,6 @@ "message": "Lipiți aici șirul de chei private:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "Copiați-vă expresia seed aici!" - }, "pending": { "message": "în așteptare" }, @@ -1173,9 +1137,6 @@ "storePhrase": { "message": "Păstrați această expresie într-un program de gestionare a parolelor cum ar fi 1Password." }, - "submit": { - "message": "Trimite" - }, "submitted": { "message": "Trimis" }, @@ -1337,9 +1298,6 @@ "userName": { "message": "Nume utilizator" }, - "validFileImport": { - "message": "Trebuie să selectați un fișier valabil pentru importare." - }, "viewAccount": { "message": "Afișați contul" }, diff --git a/app/_locales/ru/messages.json b/app/_locales/ru/messages.json index 082e450da..43249d6bc 100644 --- a/app/_locales/ru/messages.json +++ b/app/_locales/ru/messages.json @@ -1,16 +1,7 @@ { - "confirmClear": { - "message": "Вы уверены, что хотите очистить утвержденные веб-сайты?Tem certeza de que deseja limpar sites aprovados?" - }, - "clearApprovalData": { - "message": "Четкие данные об утверждении" - }, "reject": { "message": "Отклонить" }, - "providerRequestInfo": { - "message": "Домен, указанный ниже, пытается запросить доступ к API-интерфейсу Ethereum, чтобы он мог взаимодействовать с блокчейном Ethereum. Всегда проверяйте, что вы находитесь на правильном сайте, прежде чем одобрять доступ к веб-сайту." - }, "account": { "message": "Счет" }, @@ -58,10 +49,6 @@ "balanceIsInsufficientGas": { "message": "Недостаточный баланс для текущего объема газа" }, - "betweenMinAndMax": { - "message": "должно быть больше или равно $1 и меньше или равно $2.", - "description": "helper for inputting hex as decimal input" - }, "blockiesIdenticon": { "message": "Использовать Blockies Identicon" }, @@ -116,9 +103,6 @@ "copiedExclamation": { "message": "Скопировано!" }, - "copy": { - "message": "Скопировать" - }, "copyToClipboard": { "message": "Скопировать в буфер обмена" }, @@ -230,10 +214,6 @@ "message": "Получить Ether из крана для $1", "description": "Displays network name for Ether faucet" }, - "greaterThanMin": { - "message": "должно быть больше или равно $1.", - "description": "helper for inputting hex as decimal input" - }, "here": { "message": "тут", "description": "as in -click here- for more information (goes with troubleTokenBalances)" @@ -298,10 +278,6 @@ "learnMore": { "message": "Узнать больше." }, - "lessThanMax": { - "message": "должно быть меньше или равно $1.", - "description": "helper for inputting hex as decimal input" - }, "likeToAddTokens": { "message": "Вы хотите добавить эти токены?" }, @@ -377,9 +353,6 @@ "message": "Вставьте ваш закрытый ключ тут:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "Вставьте вашу ключевую фразу!" - }, "personalAddressDetected": { "message": "Обнаружен персональный адрес. Введите адрес контракта токена." }, @@ -492,9 +465,6 @@ "stateLogError": { "message": "Ошибка при получении журнала состояния." }, - "submit": { - "message": "Отправить" - }, "submitted": { "message": "Отправлена" }, @@ -548,9 +518,6 @@ "usedByClients": { "message": "Используется различными клиентами" }, - "validFileImport": { - "message": "Вам нужно выбрать правильный файл для импорта." - }, "viewAccount": { "message": "Посмотреть счет" }, @@ -569,16 +536,13 @@ "youSign": { "message": "Вы подписываете" }, - "privacyModeDefault": { - "message": "Режим конфиденциальности теперь включен по умолчанию" - }, "chartOnlyAvailableEth": { "message": "Диаграмма доступна только в сетях Ethereum." }, "contractInteraction": { "message": "Взаимодействие с контрактом" }, - "providerRequest": { + "likeToConnect": { "message": "$1 запрашивает доступ к вашему аккаунту" }, "about": { @@ -731,9 +695,6 @@ "connect": { "message": "Подключиться" }, - "connectRequest": { - "message": "Запрос на подключение" - }, "connectingTo": { "message": "Подключение к $1" }, @@ -776,9 +737,6 @@ "deleteAccount": { "message": "Удалить аккаунт" }, - "dismiss": { - "message": "Отклюнить" - }, "downloadGoogleChrome": { "message": "Скачать Google Chrome" }, diff --git a/app/_locales/sk/messages.json b/app/_locales/sk/messages.json index 697c58936..d975048d8 100644 --- a/app/_locales/sk/messages.json +++ b/app/_locales/sk/messages.json @@ -1,33 +1,21 @@ { - "privacyModeDefault": { - "message": "Režim súkromia je povolený. Je prednastavený automaticky" - }, "chartOnlyAvailableEth": { "message": "Graf je k dispozícii iba v sieťach Ethereum." }, - "confirmClear": { - "message": "Naozaj chcete vymazať schválené webové stránky?" - }, "contractInteraction": { "message": "Zmluvná interakcia" }, - "clearApprovalData": { - "message": "Jasné údaje o schválení" - }, "reject": { "message": "Odmítnout" }, - "providerRequest": { + "likeToConnect": { "message": "$1 sa chce pripojiť k vášmu účtu" }, - "providerRequestInfo": { - "message": "Níže uvedená doména se pokouší požádat o přístup k API Ethereum, aby mohla komunikovat s blokádou Ethereum. Před schválením přístupu Ethereum vždy zkontrolujte, zda jste na správném místě." - }, "about": { "message": "Informácie" }, "aboutSettingsDescription": { - "message": "Verzia, centrum podpory a kontaktné informácie." + "message": "Verzia, centrum podpory a kontaktné informácie" }, "acceleratingATransaction": { "message": "*Urýchlenie transakcie pomocou vyššej ceny za GAS zvyšuje šance na rýchlejšie spracovanie v sieti, nie je to však vždy zaručené." @@ -63,7 +51,7 @@ "message": "Rozšírené" }, "advancedSettingsDescription": { - "message": "Získajte prístup k vývojárskym funkciám, sťahujte si Stavové denníky, resetujte účet, nastavujte testovacie siete a vlastné RPC." + "message": "Získajte prístup k vývojárskym funkciám, sťahujte si Stavové denníky, resetujte účet, nastavujte testovacie siete a vlastné RPC" }, "advancedOptions": { "message": "Rozšírené nastavenia" @@ -154,10 +142,6 @@ "basic": { "message": "Základné" }, - "betweenMinAndMax": { - "message": "musí být větší nebo roven $1 a menší nebo roven $2.", - "description": "helper for inputting hex as decimal input" - }, "blockExplorerView": { "message": "Zobraziť účet na $1", "description": "$1 replaced by URL for custom block explorer" @@ -243,9 +227,6 @@ "connect": { "message": "Pripojenie" }, - "connectRequest": { - "message": "Požiadavka na pripojenie" - }, "connectingTo": { "message": "Pripája sa k $1" }, @@ -285,9 +266,6 @@ "copiedExclamation": { "message": "Zkopírováno!" }, - "copy": { - "message": "Kopírovat" - }, "copyAddress": { "message": "Kopírovať adresu do schránky" }, @@ -366,9 +344,6 @@ "directDepositEtherExplainer": { "message": "Pokud už vlastníte nějaký Ether, nejrychleji ho dostanete do peněženky přímým vkladem." }, - "dismiss": { - "message": "Zatvoriť" - }, "done": { "message": "Hotovo" }, @@ -534,10 +509,6 @@ "getStarted": { "message": "Začať" }, - "greaterThanMin": { - "message": "musí být větší nebo roven $1.", - "description": "helper for inputting hex as decimal input" - }, "happyToSeeYou": { "message": "Sme radi, že vás vidíme." }, @@ -643,10 +614,6 @@ "ledgerAccountRestriction": { "message": "Skôr ako budete môcť pridať nový účet, musíte použiť svoj posledný účet." }, - "lessThanMax": { - "message": "musí být menší nebo roven $1.", - "description": "helper for inputting hex as decimal input" - }, "letsGoSetUp": { "message": "Áno, poďme to nastaviť!" }, @@ -836,9 +803,6 @@ "message": "Vložte zde svůj privátní klíč:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "Svou klíčovou frázi vložte zde!" - }, "pending": { "message": "prebieha" }, @@ -1146,9 +1110,6 @@ "storePhrase": { "message": "Túto frázu uložte do správcu hesiel ako 1Password." }, - "submit": { - "message": "Odeslat" - }, "submitted": { "message": "Odesláno" }, @@ -1310,9 +1271,6 @@ "userName": { "message": "Meno používateľa" }, - "validFileImport": { - "message": "Musíte vybrat validní soubor k importu." - }, "viewAccount": { "message": "Zobrazit účet" }, diff --git a/app/_locales/sl/messages.json b/app/_locales/sl/messages.json index 643e62c23..312bcecfb 100644 --- a/app/_locales/sl/messages.json +++ b/app/_locales/sl/messages.json @@ -1,33 +1,21 @@ { - "privacyModeDefault": { - "message": "Zasebnostni način je zdaj privzeto omogočen" - }, "chartOnlyAvailableEth": { "message": "Grafikon na voljo le v glavnih omrežjih." }, - "confirmClear": { - "message": "Ste prepričani da želite počistiti odobrene spletne strani?" - }, "contractInteraction": { "message": "Interakcija s pogodbo" }, - "clearApprovalData": { - "message": "Počisti podatke o odobritvi" - }, "reject": { "message": "Zavrni" }, - "providerRequest": { + "likeToConnect": { "message": "$1 se želi povezati z vašim računom." }, - "providerRequestInfo": { - "message": "Domena zahteva dostop do verige blokov in ogled vašega računa. Pred potrditvjo vedno preverite ali ste na želeni spletni strani." - }, "about": { "message": "O možnostih" }, "aboutSettingsDescription": { - "message": "Različica, center za podporo in podatki za stik." + "message": "Različica, center za podporo in podatki za stik" }, "acceleratingATransaction": { "message": "* Pospešitev transakcije z višjo gas ceno poveča njene možnosti za hitrejšo obdelavo v omrežju, vendar ni vedno zagotovljena." @@ -63,7 +51,7 @@ "message": "Napredno" }, "advancedSettingsDescription": { - "message": "Dostopite do funkcij razvijalca, prenesite dnevnike držav, ponastavite račun, nastavite testne mreže in RPC po meri." + "message": "Dostopite do funkcij razvijalca, prenesite dnevnike držav, ponastavite račun, nastavite testne mreže in RPC po meri" }, "advancedOptions": { "message": "Napredne možnosti" @@ -157,10 +145,6 @@ "basic": { "message": "Osnovno" }, - "betweenMinAndMax": { - "message": "mora biti večji ali enak $1 in manjši ali enak $1.", - "description": "helper for inputting hex as decimal input" - }, "blockExplorerUrl": { "message": "Blokiraj Explorer" }, @@ -252,9 +236,6 @@ "connect": { "message": "Poveži" }, - "connectRequest": { - "message": "Zahteva za povezavo" - }, "connectingTo": { "message": "Povezovanje na $1" }, @@ -294,9 +275,6 @@ "copiedExclamation": { "message": "Kopirano!" }, - "copy": { - "message": "Kopiraj" - }, "copyAddress": { "message": "Kopiraj naslov v odložišče" }, @@ -375,9 +353,6 @@ "directDepositEtherExplainer": { "message": "Če že imate Ether, ga lahko najhitreje dobite v MetaMask z neposrednim vplačilom." }, - "dismiss": { - "message": "Opusti" - }, "done": { "message": "Končano" }, @@ -537,10 +512,6 @@ "getStarted": { "message": "Začnite" }, - "greaterThanMin": { - "message": "mora biti večji ali enak $1.", - "description": "helper for inputting hex as decimal input" - }, "happyToSeeYou": { "message": "Veseli nas, da ste nas spet obiskali." }, @@ -653,10 +624,6 @@ "ledgerAccountRestriction": { "message": "Za dodajanje novega računa morate uporabiti zadnji račun." }, - "lessThanMax": { - "message": "mora biti manjši ali enak $1.", - "description": "helper for inputting hex as decimal input" - }, "letsGoSetUp": { "message": "Lotimo se nastavitev!" }, @@ -855,9 +822,6 @@ "message": "Tukaj prilepite vaš zasebni ključ:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "Tukaj prilepite seed phase!" - }, "pending": { "message": "v obdelavi" }, @@ -1168,9 +1132,6 @@ "storePhrase": { "message": "To geslo shranite v upravitelja gesel, kot je 1Password." }, - "submit": { - "message": "Potrdi" - }, "submitted": { "message": "Potrjeno" }, @@ -1338,9 +1299,6 @@ "userName": { "message": "Uporabniško ime" }, - "validFileImport": { - "message": "Za uvoz morate izbrati pravilno datoteko." - }, "viewAccount": { "message": "Poglej račun" }, diff --git a/app/_locales/sr/messages.json b/app/_locales/sr/messages.json index 1cd653ed7..f4084996f 100644 --- a/app/_locales/sr/messages.json +++ b/app/_locales/sr/messages.json @@ -1,33 +1,21 @@ { - "privacyModeDefault": { - "message": "Režim privatnosti je podrazumevano omogućen" - }, "chartOnlyAvailableEth": { "message": "Grafikon dostupan jedino na mrežama Ethereum." }, - "confirmClear": { - "message": "Da li ste sigurni da želite da obrišete odobrene veb lokacije?" - }, "contractInteraction": { "message": "Ugovorna interakcija" }, - "clearApprovalData": { - "message": "Obrišite privatne podatke" - }, "reject": { "message": "Одбиј" }, - "providerRequest": { + "likeToConnect": { "message": "$1 bi hteo da se poveže sa vašim nalogom" }, - "providerRequestInfo": { - "message": "Ovaj sajt traži pristup kako bi video vašu trenutnu adresu naloga. Uvek budite sigurni da verujete sajtovima s kojima komunicirate." - }, "about": { "message": "Основни подаци" }, "aboutSettingsDescription": { - "message": "Verzija, centar za podršku i podaci za kontakt." + "message": "Verzija, centar za podršku i podaci za kontakt" }, "acceleratingATransaction": { "message": "* Time što se ubrzava transakcija koristeći veću gas cenu, povećavaju se šanse da se procesuira brže od strane mreže, ali to nije uvek zagarantovano." @@ -63,7 +51,7 @@ "message": "Напредне опције" }, "advancedSettingsDescription": { - "message": "Pristupite funkcijama za programere, preuzmite državne evidencije, resetujte nalog, postavite testne mreže i prilagođeni RPC." + "message": "Pristupite funkcijama za programere, preuzmite državne evidencije, resetujte nalog, postavite testne mreže i prilagođeni RPC" }, "advancedOptions": { "message": "Dodatne opcije" @@ -157,10 +145,6 @@ "basic": { "message": "Основна" }, - "betweenMinAndMax": { - "message": "mora biti veće od ili jednako $1 i manje od ili jednako $2", - "description": "helper for inputting hex as decimal input" - }, "blockExplorerUrl": { "message": "Blok istraživač" }, @@ -249,9 +233,6 @@ "connect": { "message": "Повезивање" }, - "connectRequest": { - "message": "Zahtev za povezivanjem" - }, "connectingTo": { "message": "Povezuje se na $1" }, @@ -291,9 +272,6 @@ "copiedExclamation": { "message": "Kopirano!" }, - "copy": { - "message": "Копирај" - }, "copyAddress": { "message": "Kopirajte adresu u ostavu" }, @@ -372,9 +350,6 @@ "directDepositEtherExplainer": { "message": "Ako već imate neki Ether, najbrži način da preuzmete Ether u svoj novi novčanik jeste direktnim deponovanjem." }, - "dismiss": { - "message": "Одбаци" - }, "done": { "message": "Gotovo" }, @@ -540,10 +515,6 @@ "getStarted": { "message": "Започнимо" }, - "greaterThanMin": { - "message": "mora biti veći od ili jednak $1.", - "description": "helper for inputting hex as decimal input" - }, "happyToSeeYou": { "message": "Drago nam je da vas vidimo." }, @@ -662,10 +633,6 @@ "ledgerAccountRestriction": { "message": "Treba da koristite svoj poslednji nalog pre nego što budete mogli da dodate novi." }, - "lessThanMax": { - "message": "mora biti manje od ili jednako $1.", - "description": "helper for inputting hex as decimal input" - }, "letsGoSetUp": { "message": "Da, hajde da sve podesimo!" }, @@ -861,9 +828,6 @@ "message": "Ovde nalepite vaš niz privatnog ključa", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "Nalepite ovde svoju početnu frazu!" - }, "pending": { "message": "u toku" }, @@ -1177,9 +1141,6 @@ "storePhrase": { "message": "Čuvajte ovaj izraz u menadžeru šifri kao što je 1Password." }, - "submit": { - "message": "Пошаљи" - }, "submitted": { "message": "Prosleđeno" }, @@ -1341,9 +1302,6 @@ "userName": { "message": "Корисничко име" }, - "validFileImport": { - "message": "Morate odabrati važeću datoteku da biste ih uvezli." - }, "viewAccount": { "message": "Прикажи налог" }, diff --git a/app/_locales/sv/messages.json b/app/_locales/sv/messages.json index 6d89b6e5c..750357045 100644 --- a/app/_locales/sv/messages.json +++ b/app/_locales/sv/messages.json @@ -1,19 +1,10 @@ { - "privacyModeDefault": { - "message": "Integritetsläge är nu aktiverat som standard" - }, "chartOnlyAvailableEth": { "message": "Tabellen är endast tillgänglig på Ethereum-nätverk." }, - "confirmClear": { - "message": "Är du säker på att du vill rensa godkända webbplatser?" - }, "contractInteraction": { "message": "Kontraktinteraktion" }, - "clearApprovalData": { - "message": "Rensa personlig data" - }, "appName": { "message": "MetaMask", "description": "The name of the application" @@ -21,17 +12,14 @@ "reject": { "message": "Avvisa" }, - "providerRequest": { + "likeToConnect": { "message": "$1 vill ansluta till ditt konto" }, - "providerRequestInfo": { - "message": "Den här sidan begär åtkomst till din aktuella kontoadress. Se till att du kan lita på de sidor du använder dig av." - }, "about": { "message": "Om" }, "aboutSettingsDescription": { - "message": "Version, supportcenter och kontaktinformation." + "message": "Version, supportcenter och kontaktinformation" }, "acceleratingATransaction": { "message": "* Att snabba upp en överföring genom att använda ett högre gaspris ökar chanserna för att överföringen ska hanteras snabbare av nätverket, men det är inte en garanti." @@ -67,7 +55,7 @@ "message": "Avancerat" }, "advancedSettingsDescription": { - "message": "Åtkomst till verktyg för utvecklare, ladda ner loggar, återställ konto, upprätta testnätverk och skräddarsy RPC." + "message": "Åtkomst till verktyg för utvecklare, ladda ner loggar, återställ konto, upprätta testnätverk och skräddarsy RPC" }, "advancedOptions": { "message": "Avancerade alternativ" @@ -157,10 +145,6 @@ "basic": { "message": "Grunder" }, - "betweenMinAndMax": { - "message": "måste vara större än/lika med $1, eller mindre än/lika med $2.", - "description": "helper for inputting hex as decimal input" - }, "blockExplorerUrl": { "message": "Blockera Utforskaren" }, @@ -246,9 +230,6 @@ "connect": { "message": "Ansluta" }, - "connectRequest": { - "message": "Anslutningsförfrågan" - }, "connectingTo": { "message": "Ansluter till $1" }, @@ -288,9 +269,6 @@ "copiedExclamation": { "message": "Kopierades!" }, - "copy": { - "message": "Kopiera" - }, "copyAddress": { "message": "Kopiera adress till urklipp" }, @@ -369,9 +347,6 @@ "directDepositEtherExplainer": { "message": "Om du redan har Ether är det snabbaste sättet att få Ether i din nya plånbok att göra en direktinsättning." }, - "dismiss": { - "message": "Ta bort permanent" - }, "done": { "message": "Klart" }, @@ -533,10 +508,6 @@ "getStarted": { "message": "Komma igång" }, - "greaterThanMin": { - "message": "måste vara större än eller lika med $1.", - "description": "helper for inputting hex as decimal input" - }, "happyToSeeYou": { "message": "Vi är glada att se dig." }, @@ -655,10 +626,6 @@ "ledgerAccountRestriction": { "message": "Du måste använda ditt senaste konto innan du kan lägga till ett nytt." }, - "lessThanMax": { - "message": "måste vara mindre än eller lika med $1.", - "description": "helper for inputting hex as decimal input" - }, "letsGoSetUp": { "message": "Ja, kör igång!" }, @@ -854,9 +821,6 @@ "message": "Klistra in din privata nyckel här:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "Klistra in din nyckelfras här!" - }, "pending": { "message": "väntar" }, @@ -1170,9 +1134,6 @@ "storePhrase": { "message": "Lagra denna fras i en lösenordshanterare såsom 1Password." }, - "submit": { - "message": "Skicka" - }, "submitted": { "message": "Inskickat" }, @@ -1331,9 +1292,6 @@ "userName": { "message": "Användarnamn" }, - "validFileImport": { - "message": "Du måste välja en giltig fil för att importera." - }, "viewAccount": { "message": "Visa konto" }, diff --git a/app/_locales/sw/messages.json b/app/_locales/sw/messages.json index 76d1897ac..38c89aca8 100644 --- a/app/_locales/sw/messages.json +++ b/app/_locales/sw/messages.json @@ -1,19 +1,10 @@ { - "privacyModeDefault": { - "message": "Hali ya Faragha sasa imewezeshwa kwa chaguomsingi" - }, "chartOnlyAvailableEth": { "message": "Zogoa inapatikana kwenye mitandao ya Ethereum pekee." }, - "confirmClear": { - "message": "Una uhakika unataka kufuta tovuti zilizodihinishwa?" - }, "contractInteraction": { "message": "Mwingiliono wa Mkataba" }, - "clearApprovalData": { - "message": "Futa Data za Faragha" - }, "appName": { "message": "MetaMask", "description": "The name of the application" @@ -21,17 +12,14 @@ "reject": { "message": "Kataa" }, - "providerRequest": { + "likeToConnect": { "message": "$1 ingependa kuunganishwa kwenye akaunti yako" }, - "providerRequestInfo": { - "message": "Tovuti hii inaomba idhini ya kuangalia anwani yako ya akaunti ya sasa. Daima hakikisha unaziamami tovuti ambazo unaingiliana nazo." - }, "about": { "message": "Kuhusu" }, "aboutSettingsDescription": { - "message": "Toleo, kituo cha msaada, na taarifa za mawasiliano." + "message": "Toleo, kituo cha msaada, na taarifa za mawasiliano" }, "acceleratingATransaction": { "message": "*Kuwezesha muamala kwa kutumia bei ya juu ya gesi huongeza uwezekano wake wa kushughulikiwa na mtandao haraka, lakini hauhakikishiwi siku zote." @@ -67,7 +55,7 @@ "message": "Mipangilio ya kina" }, "advancedSettingsDescription": { - "message": "Vipengele vya idhini ya msanidi, Kumbukumbu za Hali ya kupakua, Kufuta Akaunti, mitando ya majaribio ya kuweka mipangilio na RPC maalumu." + "message": "Vipengele vya idhini ya msanidi, Kumbukumbu za Hali ya kupakua, Kufuta Akaunti, mitando ya majaribio ya kuweka mipangilio na RPC maalumu" }, "advancedOptions": { "message": "Machaguo ya Juu" @@ -157,10 +145,6 @@ "basic": { "message": "Msingi" }, - "betweenMinAndMax": { - "message": "inapaswa kuwa kubwa kuliko au sawa na $1 na ndogo kuliko au sawa na $2.", - "description": "helper for inputting hex as decimal input" - }, "blockExplorerView": { "message": "Tazama akaunti kwenye $1", "description": "$1 replaced by URL for custom block explorer" @@ -246,9 +230,6 @@ "connect": { "message": "Unganisha" }, - "connectRequest": { - "message": "Unganisha Ombi" - }, "connectingTo": { "message": "Inaunganisha kwenye $1" }, @@ -288,9 +269,6 @@ "copiedExclamation": { "message": "Imenakiliwa!" }, - "copy": { - "message": "Nakili" - }, "copyAddress": { "message": "Nakili anwani kwenye ubao wa kunakilia" }, @@ -369,9 +347,6 @@ "directDepositEtherExplainer": { "message": "Ikiwa tayari una sarafu kadhaa za Ether, njia rahisi ya kupata Ether kwenye waleti yako mpya kupitia kuweka moja kwa moja." }, - "dismiss": { - "message": "Ondoa" - }, "done": { "message": "Imekamilika" }, @@ -533,10 +508,6 @@ "getStarted": { "message": "Anza" }, - "greaterThanMin": { - "message": "inapaswa iwe kubwa kuliko au sawa na $1.", - "description": "helper for inputting hex as decimal input" - }, "happyToSeeYou": { "message": "Tuna furaha kukuona" }, @@ -649,10 +620,6 @@ "ledgerAccountRestriction": { "message": "Unapaswa kutumia akaunti yako ya mwisho kabla hujaongeza mpya." }, - "lessThanMax": { - "message": "inapaswa kuwa ndogo kuliko au sawa na $1. ", - "description": "helper for inputting hex as decimal input" - }, "letsGoSetUp": { "message": "Ndiyo, hebu tuweke mipangilio!" }, @@ -848,9 +815,6 @@ "message": "Bandika uzi wako wa ufunguo binafsi hapa:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "Bandika kirai kianzio chako hapa!" - }, "pending": { "message": "inasubiri" }, @@ -1164,9 +1128,6 @@ "storePhrase": { "message": "Hifadhi kirai hiki kwenye kidhibiti nenosiri kama vile 1Password." }, - "submit": { - "message": "Wasilisha" - }, "submitted": { "message": "Imewasilishwa" }, @@ -1334,9 +1295,6 @@ "userName": { "message": "Jina la mtumiaji" }, - "validFileImport": { - "message": "Ni lazima uchague faili halali ili uhamishe." - }, "viewAccount": { "message": "Angalia Akaunti" }, diff --git a/app/_locales/ta/messages.json b/app/_locales/ta/messages.json index b5be67520..dc48abb02 100644 --- a/app/_locales/ta/messages.json +++ b/app/_locales/ta/messages.json @@ -1,19 +1,10 @@ { - "confirmClear": { - "message": "அங்கீகரிக்கப்பட்ட வலைத்தளங்களை நிச்சயமாக நீக்க விரும்புகிறீர்களா?" - }, - "clearApprovalData": { - "message": "ஒப்புதல் தரவை அழி" - }, "approve": { "message": "ஒப்புதல்" }, "reject": { "message": "நிராகரி" }, - "providerRequestInfo": { - "message": "கீழே பட்டியலிடப்பட்டுள்ள டொமைன் Web3 ஏபிஐ அணுகலைக் கோருவதற்கு முயற்சிக்கிறது, எனவே இது Ethereum blockchain உடன் தொடர்பு கொள்ள முடியும். Web3 அணுகுமுறையை அங்கீகரிப்பதற்கு முன் சரியான தளத்தில் இருப்பதை எப்போதும் இருமுறை சரிபார்க்கவும்." - }, "account": { "message": "கணக்கு" }, @@ -58,10 +49,6 @@ "balanceIsInsufficientGas": { "message": "நடப்பு வாயு மொத்தம் போதுமான சமநிலை" }, - "betweenMinAndMax": { - "message": "$ 1 க்கும் அதிகமாகவும் அல்லது $ 2 க்கு சமமாகவும் இருக்க வேண்டும்.", - "description": "ஹெக்ஸ் உள்ளீடு தசம உள்ளீடு என உதவி" - }, "blockiesIdenticon": { "message": "ப்ளாக்கிஸ் ஐடென்டிகோன் பயன்பாட்டு" }, @@ -104,9 +91,6 @@ "copiedExclamation": { "message": "நகலெடுக்கப்பட்டன!" }, - "copy": { - "message": "நகலெடு" - }, "copyToClipboard": { "message": "கிளிப்போர்டுக்கு நகலெடு" }, @@ -224,10 +208,6 @@ "message": "$ 1 க்கு ஒரு குழாய் இருந்து ஈதர் கிடைக்கும்$1", "description": "ஈத்தர் குழாய் ஐந்து பிணைய பெயர் காட்டுகிறது" }, - "greaterThanMin": { - "message": "$ 1 க்கும் அதிகமாகவோ அல்லது சமமாகவோ இருக்க வேண்டும்", - "description": "ஹெக்ஸ் உள்ளீடு தசம உள்ளீடு என உதவி" - }, "here": { "message": "இங்கே", "description": "இங்கே-கிளிக் செய்யவும்- மேலும் தகவலுக்கு (troubleTokenBalances செல்கிறது)" @@ -292,10 +272,6 @@ "learnMore": { "message": "மேலும் அறிக" }, - "lessThanMax": { - "message": "$ 1 க்கும் குறைவாகவோ அல்லது சமமாகவோ இருக்க வேண்டும்.", - "description": "ஹெக்ஸ் உள்ளீடு தசம உள்ளீடு என உதவி" - }, "likeToAddTokens": { "message": "இந்த டோக்கன்களைச் சேர்க்க விரும்புகிறீர்களா?" }, @@ -374,9 +350,6 @@ "message": "இங்கே உங்கள் தனிப்பட்ட விசை சரத்தை ஒட்டுக:", "description": "ஒரு தனிப்பட்ட விசை ஒரு கணக்கை இறக்குமதி செய்ய" }, - "pasteSeed": { - "message": "இங்கே உங்கள் விதை சொற்றொடரை ஒட்டவும்!" - }, "personalAddressDetected": { "message": "தனிப்பட்ட முகவரி கண்டறியப்பட்டது. டோக்கன் ஒப்பந்த முகவரியை உள்ளிடவும்." }, @@ -501,9 +474,6 @@ "stateLogError": { "message": "மாநில பதிவுகளை மீட்டெடுப்பதில் பிழை." }, - "submit": { - "message": "சமர்ப்பி" - }, "submitted": { "message": "சமர்ப்பிக்கப்பட்டது" }, @@ -560,9 +530,6 @@ "usedByClients": { "message": "பல்வேறு வாடிக்கையாளர்கள் பல்வேறு பயன்படுத்திய" }, - "validFileImport": { - "message": "இறக்குமதி செய்ய சரியான கோப்பு தேர்ந்தெடுக்க வேண்டும்." - }, "viewAccount": { "message": "கணக்கைப் பார்" }, @@ -602,9 +569,6 @@ "delete": { "message": "நீக்கு" }, - "dismiss": { - "message": "நிராகரி" - }, "fast": { "message": "வேகமான" }, diff --git a/app/_locales/te/messages.json b/app/_locales/te/messages.json index ba588ca46..2df11217d 100644 --- a/app/_locales/te/messages.json +++ b/app/_locales/te/messages.json @@ -42,9 +42,6 @@ "connect": { "message": "కనెక్ట్ చేయండి" }, - "copy": { - "message": "కాపీ చెయ్యి" - }, "copyToClipboard": { "message": "క్లిప్‌బోర్డ్‌కు కాపీ చేయి" }, @@ -57,9 +54,6 @@ "details": { "message": "వివరాలు" }, - "dismiss": { - "message": "తొలగించు" - }, "done": { "message": "పూర్తయింది" }, @@ -131,9 +125,6 @@ "settings": { "message": "సెట్టింగ్‌లు" }, - "submit": { - "message": "సమర్పించు" - }, "tryAgain": { "message": "మళ్లీ ప్రయత్నించు" }, diff --git a/app/_locales/th/messages.json b/app/_locales/th/messages.json index f5fcf3edf..6b3686f45 100644 --- a/app/_locales/th/messages.json +++ b/app/_locales/th/messages.json @@ -1,19 +1,10 @@ { - "confirmClear": { - "message": "คุณแน่ใจหรือไม่ว่าต้องการล้างเว็บไซต์ที่ผ่านการอนุมัติ" - }, - "clearApprovalData": { - "message": "ล้างข้อมูลการอนุมัติ" - }, "reject": { "message": "ปฏิเสธ" }, - "providerRequest": { + "likeToConnect": { "message": "$1 ต้องการเชื่อมต่อกับบัญชีของคุณ" }, - "providerRequestInfo": { - "message": "โดเมนที่แสดงด้านล่างกำลังพยายามขอเข้าถึง API ของ Ethereum เพื่อให้สามารถโต้ตอบกับบล็อค Ethereum ได้ ตรวจสอบว่าคุณอยู่ในไซต์ที่ถูกต้องก่อนที่จะอนุมัติการเข้าถึง Ethereum เสมอ" - }, "about": { "message": "เกี่ยวกับ" }, @@ -64,10 +55,6 @@ "balanceIsInsufficientGas": { "message": "ยอดคงเหลือไม่พอสำหรับจ่ายค่าแก๊สทั้งหมด" }, - "betweenMinAndMax": { - "message": "ต้องมากกว่าหรือเท่ากับ $1 และน้อยกว่าหรือเท่ากับ $2", - "description": "helper for inputting hex as decimal input" - }, "blockiesIdenticon": { "message": "ใช้งาน Blockies Identicon" }, @@ -131,9 +118,6 @@ "copiedExclamation": { "message": "คัดลอกแล้ว!" }, - "copy": { - "message": "คัดลอก" - }, "copyToClipboard": { "message": "คัดลอกไปคลิปบอร์ด" }, @@ -191,9 +175,6 @@ "directDepositEtherExplainer": { "message": "ถ้าคุณมีอีเธอร์อยู่แล้ววิธีการที่เร็วที่สุดในการเอาเงินเข้ากระเป๋าใหม่ก็คือการโอนตรงๆ" }, - "dismiss": { - "message": "ปิด" - }, "done": { "message": "เสร็จสิ้น" }, @@ -284,10 +265,6 @@ "message": "รับอีเธอร์ที่ปล่อยจาก $1", "description": "Displays network name for Ether faucet" }, - "greaterThanMin": { - "message": "ต้องมากกว่าหรือเท่ากับ $1.", - "description": "helper for inputting hex as decimal input" - }, "here": { "message": "ที่นี่", "description": "as in -click here- for more information (goes with troubleTokenBalances)" @@ -352,10 +329,6 @@ "learnMore": { "message": "เรียนรู้เพิ่มเติม" }, - "lessThanMax": { - "message": "ต้องน้อยกว่าหรือเท่ากับ $1.", - "description": "helper for inputting hex as decimal input" - }, "likeToAddTokens": { "message": "คุณต้องการเพิ่มโทเค็นเหล่านี้หรือไม่?" }, @@ -443,9 +416,6 @@ "message": "วางคีย์ส่วนตัวของคุณที่นี่:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "วางคำชีดของคุณที่นี่!" - }, "personalAddressDetected": { "message": "ตรวจพบแอดแดรสส่วนตัวแล้ว ใส่แอดแดรสสัญญาโทเค็น" }, @@ -588,9 +558,6 @@ "storePhrase": { "message": "เก็บ Phrase นี้ในตัวจัดการรหัสผ่าน เช่น 1Password" }, - "submit": { - "message": "ตกลง" - }, "supportCenter": { "message": "ไปที่ศูนย์สนับสนุนของเรา" }, @@ -662,9 +629,6 @@ "usedByClients": { "message": "ถูกใช้งานโดยหลายไคลเอนท์" }, - "validFileImport": { - "message": "คุณต้องเลือกไฟล์ที่ถูกต้องเพื่อนำเข้า" - }, "viewAccount": { "message": "ดูบัญชี" }, diff --git a/app/_locales/tr/messages.json b/app/_locales/tr/messages.json index fe2332f2f..fdb1ae927 100644 --- a/app/_locales/tr/messages.json +++ b/app/_locales/tr/messages.json @@ -1,16 +1,7 @@ { - "confirmClear": { - "message": "Onaylanmış web sitelerini silmek istediğinizden emin misiniz?" - }, - "clearApprovalData": { - "message": "Onay verilerini temizle" - }, "reject": { "message": "Reddetmek" }, - "providerRequestInfo": { - "message": "Aşağıda listelenen etki alanı, Ethereum API'sine erişim talep etmeye çalışmaktadır, böylece Ethereum blockchain ile etkileşime girebilir. Web3 erişimini onaylamadan önce her zaman doğru sitede olduğunuzu kontrol edin." - }, "account": { "message": "Hesap" }, @@ -58,10 +49,6 @@ "balanceIsInsufficientGas": { "message": "Toplam gas için yetersiz bakiye" }, - "betweenMinAndMax": { - "message": "$1'e eşit veya daha büyük olmalı ve $2'den küçük veya eşit olmalı", - "description": "helper for inputting hex as decimal input" - }, "blockiesIdenticon": { "message": "Blockies Identicon kullan" }, @@ -116,9 +103,6 @@ "copiedExclamation": { "message": "Kopyalandı!" }, - "copy": { - "message": "Kopyala" - }, "copyToClipboard": { "message": "Panoya kopyala" }, @@ -294,10 +278,6 @@ "learnMore": { "message": "Daha fazla bilgi." }, - "lessThanMax": { - "message": "$1'den az veya eşit olmalıdır.", - "description": "helper for inputting hex as decimal input" - }, "likeToAddTokens": { "message": "Bu jetonlara adres eklemek ister misiniz?" }, @@ -379,9 +359,6 @@ "message": "Özel anahtar dizinizi buraya yapıştırın:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "Kaynak ifadenizi buraya yapıştırın!" - }, "personalAddressDetected": { "message": "Kişisel adres tespit edilidi. Jeton sözleşme adresini girin." }, @@ -494,9 +471,6 @@ "stateLogError": { "message": "Durum kayıtlarını alma hatası" }, - "submit": { - "message": "Gönder" - }, "submitted": { "message": "Gönderildi" }, @@ -553,9 +527,6 @@ "usedByClients": { "message": "Farklı istemciler tarafından kullanılmakta" }, - "validFileImport": { - "message": "Almak için geçerli bir dosya seçmelisiniz" - }, "viewAccount": { "message": "Hesabı İncele" }, diff --git a/app/_locales/uk/messages.json b/app/_locales/uk/messages.json index d269044b3..cec516b6c 100644 --- a/app/_locales/uk/messages.json +++ b/app/_locales/uk/messages.json @@ -1,33 +1,21 @@ { - "privacyModeDefault": { - "message": "Режим конфіденційності тепер увімкнено за замовчуванням" - }, "chartOnlyAvailableEth": { "message": "Таблиця доступна тільки в мережах Ethereum." }, - "confirmClear": { - "message": "Ви впевнені, що хочете очистити затверджені веб-сайти?" - }, "contractInteraction": { "message": "Контрактна взаємодія" }, - "clearApprovalData": { - "message": "Очистити приватні дані" - }, "reject": { "message": "Відхилити" }, - "providerRequest": { + "likeToConnect": { "message": "$1 бажає підключитися до вашого облікового запису" }, - "providerRequestInfo": { - "message": "Цей сайт запитує доступ на перегляд вашої поточної адреси облікового запису. Завжди взаємодійте лише з веб-сайтами, яким довіряєте." - }, "about": { "message": "Про Google Chrome" }, "aboutSettingsDescription": { - "message": "Версія, центр підтримки та контактна інформація." + "message": "Версія, центр підтримки та контактна інформація" }, "acceleratingATransaction": { "message": "* Прискорення транзакції за допомогою вищих цін на газ підвищує її шанси бути обробленою мережею швидше, але це не завжди гарантовано." @@ -63,7 +51,7 @@ "message": "Розширені" }, "advancedSettingsDescription": { - "message": "Отримайте доступ до функцій розробника, завантажте Логи станів, перезапустіть обліковий запис, налаштуйте тестові сітки та персоніфіковані RPC." + "message": "Отримайте доступ до функцій розробника, завантажте Логи станів, перезапустіть обліковий запис, налаштуйте тестові сітки та персоніфіковані RPC" }, "advancedOptions": { "message": "Додаткові параметри" @@ -157,10 +145,6 @@ "basic": { "message": "Основні параметри" }, - "betweenMinAndMax": { - "message": "має бути більшим або дорівнювати $1 і меншим або дорівнювати $2.", - "description": "helper for inputting hex as decimal input" - }, "blockExplorerUrl": { "message": "Блокувати Explorer" }, @@ -252,9 +236,6 @@ "connect": { "message": "Під’єднатися" }, - "connectRequest": { - "message": "Запит на з'єднання" - }, "connectingTo": { "message": "Під'єднуємось до $1" }, @@ -294,9 +275,6 @@ "copiedExclamation": { "message": "Скопійовано!" }, - "copy": { - "message": "Копіювати" - }, "copyAddress": { "message": "Копіювати адресу в буфер обміну" }, @@ -375,9 +353,6 @@ "directDepositEtherExplainer": { "message": "Якщо ви вже маєте ефір, пряме переведення – найшвидший спосіб передати ефір у свій гаманець." }, - "dismiss": { - "message": "Відхилити" - }, "done": { "message": "Готово" }, @@ -543,10 +518,6 @@ "getStarted": { "message": "Почати" }, - "greaterThanMin": { - "message": "має бути більшим або рівним $1.", - "description": "helper for inputting hex as decimal input" - }, "happyToSeeYou": { "message": "Раді вас бачити." }, @@ -665,10 +636,6 @@ "ledgerAccountRestriction": { "message": "Потрібно скористатися своїм останнім обліковим записом, перш ніж додавати новий." }, - "lessThanMax": { - "message": "має бути більшим або дорівнювати $1.", - "description": "helper for inputting hex as decimal input" - }, "letsGoSetUp": { "message": "Так, давайте налаштуємо!" }, @@ -870,9 +837,6 @@ "message": "Вставте ваш рядок з особистим ключем сюди:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "Вставте сюди свою мнемонічну фразу!" - }, "pending": { "message": "очікує" }, @@ -1186,9 +1150,6 @@ "storePhrase": { "message": "Зберігайте цю фразу у менеджері паролів, як 1Password." }, - "submit": { - "message": "Надіслати" - }, "submitted": { "message": "Надіслано" }, @@ -1356,9 +1317,6 @@ "userName": { "message": "Ім’я користувача" }, - "validFileImport": { - "message": "Потрібно вибрати дійсний файл для імпорту." - }, "viewAccount": { "message": "Переглянути обліковий запис" }, diff --git a/app/_locales/vi/messages.json b/app/_locales/vi/messages.json index b22361678..6136403ba 100644 --- a/app/_locales/vi/messages.json +++ b/app/_locales/vi/messages.json @@ -1,16 +1,7 @@ { - "confirmClear": { - "message": "Bạn có chắc chắn muốn xóa các trang web được phê duyệt không?" - }, - "clearApprovalData": { - "message": "Xóa dữ liệu phê duyệt" - }, "reject": { "message": "Từ chối" }, - "providerRequestInfo": { - "message": "Miền được liệt kê bên dưới đang cố gắng yêu cầu quyền truy cập vào API Ethereum để nó có thể tương tác với chuỗi khối Ethereum. Luôn kiểm tra kỹ xem bạn có đang ở đúng trang web trước khi phê duyệt quyền truy cập Ethereum hay không." - }, "account": { "message": "Tài khoản" }, @@ -49,10 +40,6 @@ "balanceIsInsufficientGas": { "message": "Số dư không đủ để thanh toán tổng tiền gas hiện tại" }, - "betweenMinAndMax": { - "message": "phải nhiều hơn hoặc bằng $1 và ít hơn hoặc bằng $2.", - "description": "helper for inputting hex as decimal input" - }, "buyCoinSwitch": { "message": "Mua trên CoinSwitch" }, @@ -83,9 +70,6 @@ "copiedExclamation": { "message": "Đã sao chép!" }, - "copy": { - "message": "Sao chép" - }, "copyToClipboard": { "message": "Đã sao chép vào clipboard" }, @@ -179,10 +163,6 @@ "message": "Lấy Ether từ vòi với giá $1", "description": "Displays network name for Ether faucet" }, - "greaterThanMin": { - "message": "phải nhiều hơn hoặc bằng $1", - "description": "helper for inputting hex as decimal input" - }, "here": { "message": "tại đây", "description": "as in -click here- for more information (goes with troubleTokenBalances)" @@ -223,10 +203,6 @@ "kovan": { "message": "Mạng thử nghiệm Kovan" }, - "lessThanMax": { - "message": "phải ít hơn hoặc bằng $1.", - "description": "helper for inputting hex as decimal input" - }, "loading": { "message": "Đang tải..." }, @@ -284,9 +260,6 @@ "message": "Dán dãy khóa cá nhân của bạn tại đây:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "Dán Mật Khẩu Sinh Khoá (seed phrase) của bạn tại đây!" - }, "privateKey": { "message": "Khóa Bí Mật", "description": "select this type of file to use to import an account" @@ -348,9 +321,6 @@ "sigRequest": { "message": "Yêu cầu chữ ký" }, - "submit": { - "message": "Gửi đi" - }, "testFaucet": { "message": "Vòi nhận tiền ETH ảo để thử nghiệm" }, diff --git a/app/_locales/zh_CN/messages.json b/app/_locales/zh_CN/messages.json index 90c7642dc..e92813a9a 100644 --- a/app/_locales/zh_CN/messages.json +++ b/app/_locales/zh_CN/messages.json @@ -1,28 +1,16 @@ { - "privacyModeDefault": { - "message": "现已默认启用隐私模式" - }, "chartOnlyAvailableEth": { "message": "聊天功能仅对以太坊网络开放。" }, - "confirmClear": { - "message": "您确定要清除已批准的网站吗?" - }, "contractInteraction": { "message": "合约交互" }, - "clearApprovalData": { - "message": "清除批准数据" - }, "reject": { "message": "拒绝" }, - "providerRequest": { + "likeToConnect": { "message": "$1 希望关联您的账户" }, - "providerRequestInfo": { - "message": "下面列出的域正在尝试请求访问Ethereum API,以便它可以与以太坊区块链进行交互。在批准Ethereum访问之前,请务必仔细检查您是否在正确的站点上。" - }, "about": { "message": "关于" }, @@ -87,7 +75,7 @@ "message": "添加推荐代币" }, "addAcquiredTokens": { - "message": "在Metamask上添加已用的代币" + "message": "在MetaMask上添加已用的代币" }, "amount": { "message": "数量" @@ -157,10 +145,6 @@ "basic": { "message": "基本" }, - "betweenMinAndMax": { - "message": "必须大于等于 $1 并且小于等于 $2 。", - "description": "helper for inputting hex as decimal input" - }, "blockExplorerUrl": { "message": "屏蔽管理器" }, @@ -252,9 +236,6 @@ "connect": { "message": "连接" }, - "connectRequest": { - "message": "关联请求" - }, "connectingTo": { "message": "正在连接 $1" }, @@ -294,9 +275,6 @@ "copiedExclamation": { "message": "已复制" }, - "copy": { - "message": "复制" - }, "copyAddress": { "message": "将地址复制到剪贴板" }, @@ -375,9 +353,6 @@ "directDepositEtherExplainer": { "message": "如果你已经有了一些 Ether,通过直接转入是你的新钱包获取 Ether 的最快捷方式。" }, - "dismiss": { - "message": "关闭" - }, "done": { "message": "完成" }, @@ -537,10 +512,6 @@ "getStarted": { "message": "开始使用" }, - "greaterThanMin": { - "message": "必须要大于等于 $1。", - "description": "helper for inputting hex as decimal input" - }, "happyToSeeYou": { "message": "我们很高兴与您见面。" }, @@ -656,10 +627,6 @@ "ledgerAccountRestriction": { "message": "请在新增账户前,确认添加上一个账户。" }, - "lessThanMax": { - "message": "必须小于或等于 $1.", - "description": "helper for inputting hex as decimal input" - }, "letsGoSetUp": { "message": "是的。立即开始设置!" }, @@ -852,9 +819,6 @@ "message": "请粘贴你的私钥:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "请粘贴你的助记词!" - }, "pending": { "message": "待处理" }, @@ -1168,9 +1132,6 @@ "storePhrase": { "message": "通过如 1Password 等密码管家保存该密语。" }, - "submit": { - "message": "提交" - }, "submitted": { "message": "已提交" }, @@ -1335,9 +1296,6 @@ "userName": { "message": "用户名" }, - "validFileImport": { - "message": "您必须选择一个有效的文件进行导入." - }, "viewAccount": { "message": "查看账户" }, diff --git a/app/_locales/zh_TW/messages.json b/app/_locales/zh_TW/messages.json index 501272799..2859e8fd4 100644 --- a/app/_locales/zh_TW/messages.json +++ b/app/_locales/zh_TW/messages.json @@ -1,28 +1,16 @@ { - "privacyModeDefault": { - "message": "隱私模式現已根據預設開啟" - }, "chartOnlyAvailableEth": { "message": "圖表僅適用於以太坊網路。" }, - "confirmClear": { - "message": "您確定要清除已批准的網站紀錄?" - }, "contractInteraction": { "message": "合約互動" }, - "clearApprovalData": { - "message": "清除批准數據" - }, "reject": { "message": "拒絕" }, - "providerRequest": { + "likeToConnect": { "message": "$1 請求訪問帳戶權限" }, - "providerRequestInfo": { - "message": "此網站希望能讀取您的帳戶資訊。請務必確認您信任這個網站、並了解後續可能的交易行為。" - }, "about": { "message": "關於" }, @@ -157,10 +145,6 @@ "basic": { "message": "基本" }, - "betweenMinAndMax": { - "message": "必須大於等於 $1 並且小於等於 $2 。", - "description": "helper for inputting hex as decimal input" - }, "blockExplorerUrl": { "message": "區塊鏈瀏覽器" }, @@ -249,9 +233,6 @@ "connect": { "message": "連線" }, - "connectRequest": { - "message": "連線請求" - }, "connectingTo": { "message": "連線到$1" }, @@ -291,9 +272,6 @@ "copiedExclamation": { "message": "已複製!" }, - "copy": { - "message": "複製" - }, "copyAddress": { "message": "複製到剪貼簿" }, @@ -372,9 +350,6 @@ "directDepositEtherExplainer": { "message": "如果您已經擁有乙太幣,直接存入功能是讓新錢包最快取得乙太幣的方式。" }, - "dismiss": { - "message": "關閉" - }, "done": { "message": "完成" }, @@ -540,10 +515,6 @@ "getStarted": { "message": "開始使用" }, - "greaterThanMin": { - "message": "必須要大於等於 $1。", - "description": "helper for inputting hex as decimal input" - }, "happyToSeeYou": { "message": "我們很高興看到你。" }, @@ -662,10 +633,6 @@ "ledgerAccountRestriction": { "message": "您必須使用最後的帳戶才能產生新帳戶" }, - "lessThanMax": { - "message": "必須小於等於 $1.", - "description": "helper for inputting hex as decimal input" - }, "letsGoSetUp": { "message": "好,我們開始吧!" }, @@ -858,9 +825,6 @@ "message": "請貼上您的私鑰字串:", "description": "For importing an account from a private key" }, - "pasteSeed": { - "message": "請貼上您的助憶詞!" - }, "pending": { "message": "等待處理" }, @@ -1162,9 +1126,6 @@ "storePhrase": { "message": "您可以用密碼管理系統例如 1Password 等軟體儲存助憶詞。" }, - "submit": { - "message": "送出" - }, "submitted": { "message": "已送出" }, @@ -1329,9 +1290,6 @@ "userName": { "message": "使用者名稱" }, - "validFileImport": { - "message": "您必須選擇一個合法的檔案來匯入." - }, "viewAccount": { "message": "查看帳戶" }, diff --git a/app/images/broken-line.svg b/app/images/broken-line.svg new file mode 100644 index 000000000..ec4ed0d9c --- /dev/null +++ b/app/images/broken-line.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/images/connect-white.svg b/app/images/connect-white.svg new file mode 100644 index 000000000..e9063ae46 --- /dev/null +++ b/app/images/connect-white.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/images/provider-approval-check.svg b/app/images/permissions-check.svg similarity index 100% rename from app/images/provider-approval-check.svg rename to app/images/permissions-check.svg diff --git a/app/scripts/background.js b/app/scripts/background.js index fc14f68a7..38a58fdd9 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -64,6 +64,7 @@ const isEdge = !isIE && !!window.StyleMedia let popupIsOpen = false let notificationIsOpen = false const openMetamaskTabsIDs = {} +const requestAccountTabIds = {} // state persistence const localStore = new LocalStore() @@ -75,7 +76,6 @@ initialize().catch(log.error) // setup metamask mesh testing container const { submitMeshMetricsEntry } = setupMetamaskMeshMetrics() - /** * An object representing a transaction, in whatever state it is in. * @typedef TransactionMeta @@ -248,6 +248,12 @@ function setupController (initState, initLangCode) { // platform specific api platform, encryptor: isEdge ? new EdgeEncryptor() : undefined, + getRequestAccountTabIds: () => { + return requestAccountTabIds + }, + getOpenMetamaskTabsIds: () => { + return openMetamaskTabsIDs + }, }) const provider = controller.provider @@ -263,7 +269,9 @@ function setupController (initState, initLangCode) { // report failed transactions to Sentry controller.txController.on(`tx:status-update`, (txId, status) => { - if (status !== 'failed') return + if (status !== 'failed') { + return + } const txMeta = controller.txController.txStateManager.getTx(txId) try { reportFailedTxToSentry({ sentry, txMeta }) @@ -315,6 +323,7 @@ function setupController (initState, initLangCode) { // extension.runtime.onConnect.addListener(connectRemote) extension.runtime.onConnectExternal.addListener(connectExternal) + extension.runtime.onMessage.addListener(controller.onMessage.bind(controller)) const metamaskInternalProcessHash = { [ENVIRONMENT_TYPE_POPUP]: true, @@ -387,6 +396,17 @@ function setupController (initState, initLangCode) { }) } } else { + if (remotePort.sender && remotePort.sender.tab && remotePort.sender.url) { + const tabId = remotePort.sender.tab.id + const url = new URL(remotePort.sender.url) + const origin = url.hostname + + remotePort.onMessage.addListener(msg => { + if (msg.data && msg.data.method === 'eth_requestAccounts') { + requestAccountTabIds[origin] = tabId + } + }) + } connectExternal(remotePort) } } @@ -411,7 +431,7 @@ function setupController (initState, initLangCode) { controller.messageManager.on('updateBadge', updateBadge) controller.personalMessageManager.on('updateBadge', updateBadge) controller.typedMessageManager.on('updateBadge', updateBadge) - controller.providerApprovalController.memStore.on('update', updateBadge) + controller.permissionsController.permissions.subscribe(updateBadge) /** * Updates the Web Extension's "badge" number, on the little fox in the toolbar. @@ -423,8 +443,8 @@ function setupController (initState, initLangCode) { const unapprovedMsgCount = controller.messageManager.unapprovedMsgCount const unapprovedPersonalMsgs = controller.personalMessageManager.unapprovedPersonalMsgCount const unapprovedTypedMsgs = controller.typedMessageManager.unapprovedTypedMessagesCount - const pendingProviderRequests = controller.providerApprovalController.memStore.getState().providerRequests.length - const count = unapprovedTxCount + unapprovedMsgCount + unapprovedPersonalMsgs + unapprovedTypedMsgs + pendingProviderRequests + const pendingPermissionRequests = Object.keys(controller.permissionsController.permissions.state.permissionsRequests).length + const count = unapprovedTxCount + unapprovedMsgCount + unapprovedPersonalMsgs + unapprovedTypedMsgs + pendingPermissionRequests if (count) { label = String(count) } diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 7b302fcec..6f787d062 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -2,8 +2,8 @@ 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 { Writable } = require('readable-stream') const LocalMessageDuplexStream = require('post-message-stream') const ObjectMultiplex = require('obj-multiplex') const extension = require('extensionizer') @@ -20,7 +20,7 @@ const inpageBundle = inpageContent + inpageSuffix // If we create a FireFox-only code path using that API, // MetaMask will be much faster loading and performant on Firefox. -if (shouldInjectWeb3()) { +if (shouldInjectProvider()) { injectScript(inpageBundle) start() } @@ -39,7 +39,7 @@ function injectScript (content) { container.insertBefore(scriptTag, container.children[0]) container.removeChild(scriptTag) } catch (e) { - console.error('MetaMask script injection failed', e) + console.error('MetaMask provider injection failed.', e) } } @@ -86,6 +86,44 @@ async function setupStreams () { (err) => logStreamDisconnectWarning('MetaMask Background Multiplex', err) ) + const onboardingStream = pageMux.createStream('onboarding') + const addCurrentTab = new Writable({ + objectMode: true, + write: (chunk, _, callback) => { + if (!chunk) { + return callback(new Error('Malformed onboarding message')) + } + + const handleSendMessageResponse = (error, success) => { + if (!error && !success) { + error = extension.runtime.lastError + } + if (error) { + log.error(`Failed to send ${chunk.type} message`, error) + return callback(error) + } + callback(null) + } + + try { + if (chunk.type === 'registerOnboarding') { + extension.runtime.sendMessage({ type: 'metamask:registerOnboarding', location: window.location.href }, handleSendMessageResponse) + } else { + throw new Error(`Unrecognized onboarding message type: '${chunk.type}'`) + } + } catch (error) { + log.error(error) + return callback(error) + } + }, + }) + + pump( + onboardingStream, + addCurrentTab, + error => console.error('MetaMask onboarding channel traffic failed', error), + ) + // forward communication across inpage-background for these channels only forwardTrafficBetweenMuxers('provider', pageMux, extensionMux) forwardTrafficBetweenMuxers('publicConfig', pageMux, extensionMux) @@ -93,12 +131,6 @@ async function setupStreams () { // connect "phishing" channel to warning system const phishingStream = extensionMux.createStream('phishing') phishingStream.once('data', redirectToPhishingWarning) - - // connect "publicApi" channel to submit page metadata - const publicApiStream = extensionMux.createStream('publicApi') - const background = await setupPublicApi(publicApiStream) - - return { background } } function forwardTrafficBetweenMuxers (channelName, muxA, muxB) { @@ -112,37 +144,6 @@ function forwardTrafficBetweenMuxers (channelName, muxA, muxB) { ) } -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 -} - -/** - * Gets site metadata and returns it - * - */ -function getSiteMetadata () { - // get metadata - const metadata = { - name: getSiteName(window), - icon: getSiteIcon(window), - } - return metadata -} - /** * Error handler for page to extension stream disconnections * @@ -151,16 +152,18 @@ function getSiteMetadata () { */ function logStreamDisconnectWarning (remoteLabel, err) { let warningMsg = `MetamaskContentscript - lost connection to ${remoteLabel}` - if (err) warningMsg += '\n' + err.stack + if (err) { + warningMsg += '\n' + err.stack + } console.warn(warningMsg) } /** - * Determines if Web3 should be injected + * Determines if the provider should be injected * - * @returns {boolean} {@code true} if Web3 should be injected + * @returns {boolean} {@code true} if the provider should be injected */ -function shouldInjectWeb3 () { +function shouldInjectProvider () { return doctypeCheck() && suffixCheck() && documentElementCheck() && !blacklistedDomainCheck() } @@ -183,8 +186,8 @@ function doctypeCheck () { * Returns whether or not the extension (suffix) of the current document is prohibited * * This checks {@code window.location.pathname} against a set of file extensions - * that should not have web3 injected into them. This check is indifferent of query parameters - * in the location. + * that we should not inject the provider into. This check is indifferent of + * query parameters in the location. * * @returns {boolean} whether or not the extension of the current document is prohibited */ @@ -257,52 +260,14 @@ 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"]') - if (siteName) { - return siteName.content - } - - const metaTitle = document.querySelector('head > meta[name="title"]') - if (metaTitle) { - return metaTitle.content - } - - return document.title -} - -/** - * Extracts an icon for the site from the DOM - */ -function getSiteIcon (window) { - const document = window.document - - // Use the site's favicon if it exists - const shortcutIcon = document.querySelector('head > link[rel="shortcut icon"]') - if (shortcutIcon) { - return shortcutIcon.href - } - - // Search through available icons in no particular order - const icon = Array.from(document.querySelectorAll('head > link[rel="icon"]')).find((icon) => Boolean(icon.href)) - if (icon) { - return icon.href - } - - 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 + if (['interactive', 'complete'].includes(document.readyState)) { + return + } // wait for load - await new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve, { once: true })) + return new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve, { once: true })) } diff --git a/app/scripts/controllers/detect-tokens.js b/app/scripts/controllers/detect-tokens.js index e6e993073..923aa2d15 100644 --- a/app/scripts/controllers/detect-tokens.js +++ b/app/scripts/controllers/detect-tokens.js @@ -29,8 +29,12 @@ class DetectTokensController { * */ async detectNewTokens () { - if (!this.isActive) { return } - if (this._network.store.getState().provider.type !== MAINNET) { return } + if (!this.isActive) { + return + } + if (this._network.store.getState().provider.type !== MAINNET) { + return + } const tokensToDetect = [] this.web3.setProvider(this._network._provider) for (const contractAddress in contracts) { @@ -80,7 +84,9 @@ class DetectTokensController { * */ restartTokenDetection () { - if (!(this.isActive && this.selectedAddress)) { return } + if (!(this.isActive && this.selectedAddress)) { + return + } this.detectNewTokens() this.interval = DEFAULT_INTERVAL } @@ -90,8 +96,12 @@ class DetectTokensController { */ set interval (interval) { this._handle && clearInterval(this._handle) - if (!interval) { return } - this._handle = setInterval(() => { this.detectNewTokens() }, interval) + if (!interval) { + return + } + this._handle = setInterval(() => { + this.detectNewTokens() + }, interval) } /** @@ -99,9 +109,15 @@ class DetectTokensController { * @type {Object} */ set preferences (preferences) { - if (!preferences) { return } + if (!preferences) { + return + } this._preferences = preferences - preferences.store.subscribe(({ tokens = [] }) => { this.tokenAddresses = tokens.map((obj) => { return obj.address }) }) + preferences.store.subscribe(({ tokens = [] }) => { + this.tokenAddresses = tokens.map((obj) => { + return obj.address + }) + }) preferences.store.subscribe(({ selectedAddress }) => { if (this.selectedAddress !== selectedAddress) { this.selectedAddress = selectedAddress @@ -114,7 +130,9 @@ class DetectTokensController { * @type {Object} */ set network (network) { - if (!network) { return } + if (!network) { + return + } this._network = network this.web3 = new Web3(network._provider) } @@ -124,12 +142,16 @@ class DetectTokensController { * @type {Object} */ set keyringMemStore (keyringMemStore) { - if (!keyringMemStore) { return } + if (!keyringMemStore) { + return + } this._keyringMemStore = keyringMemStore this._keyringMemStore.subscribe(({ isUnlocked }) => { if (this.isUnlocked !== isUnlocked) { this.isUnlocked = isUnlocked - if (isUnlocked) { this.restartTokenDetection() } + if (isUnlocked) { + this.restartTokenDetection() + } } }) } diff --git a/app/scripts/controllers/incoming-transactions.js b/app/scripts/controllers/incoming-transactions.js index 029bf47aa..10735b3f9 100644 --- a/app/scripts/controllers/incoming-transactions.js +++ b/app/scripts/controllers/incoming-transactions.js @@ -163,7 +163,9 @@ class IncomingTransactionsController { const newIncomingTransactions = { ...currentIncomingTxs, } - newTxs.forEach(tx => { newIncomingTransactions[tx.hash] = tx }) + newTxs.forEach(tx => { + newIncomingTransactions[tx.hash] = tx + }) this.store.updateState({ incomingTxLastFetchedBlocksByNetwork: { diff --git a/app/scripts/controllers/network/middleware/pending.js b/app/scripts/controllers/network/middleware/pending.js index 542d8bde6..96a5d40be 100644 --- a/app/scripts/controllers/network/middleware/pending.js +++ b/app/scripts/controllers/network/middleware/pending.js @@ -4,9 +4,13 @@ const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware function createPendingNonceMiddleware ({ getPendingNonce }) { return createAsyncMiddleware(async (req, res, next) => { const {method, params} = req - if (method !== 'eth_getTransactionCount') return next() + if (method !== 'eth_getTransactionCount') { + return next() + } const [param, blockRef] = params - if (blockRef !== 'pending') return next() + if (blockRef !== 'pending') { + return next() + } res.result = await getPendingNonce(param) }) } @@ -14,10 +18,14 @@ function createPendingNonceMiddleware ({ getPendingNonce }) { function createPendingTxMiddleware ({ getPendingTransactionByHash }) { return createAsyncMiddleware(async (req, res, next) => { const {method, params} = req - if (method !== 'eth_getTransactionByHash') return next() + if (method !== 'eth_getTransactionByHash') { + return next() + } const [hash] = params const txMeta = getPendingTransactionByHash(hash) - if (!txMeta) return next() + if (!txMeta) { + return next() + } res.result = formatTxMetaForRpcResult(txMeta) }) } diff --git a/app/scripts/controllers/network/network.js b/app/scripts/controllers/network/network.js index 7b5d8ef38..d78f92f93 100644 --- a/app/scripts/controllers/network/network.js +++ b/app/scripts/controllers/network/network.js @@ -81,7 +81,9 @@ module.exports = class NetworkController extends EventEmitter { verifyNetwork () { // Check network when restoring connectivity: - if (this.isNetworkLoading()) this.lookupNetwork() + if (this.isNetworkLoading()) { + this.lookupNetwork() + } } getNetworkState () { @@ -196,7 +198,7 @@ module.exports = class NetworkController extends EventEmitter { }) this._setNetworkClient(networkClient) // setup networkConfig - var settings = { + const settings = { ticker: 'ETH', } this.networkConfig.putState(settings) @@ -219,7 +221,7 @@ module.exports = class NetworkController extends EventEmitter { nickname, } // setup networkConfig - var settings = { + let settings = { network: chainId, } settings = extend(settings, networks.networkList['rpc']) diff --git a/app/scripts/controllers/onboarding.js b/app/scripts/controllers/onboarding.js index a29c8407a..5d00fb775 100644 --- a/app/scripts/controllers/onboarding.js +++ b/app/scripts/controllers/onboarding.js @@ -1,5 +1,6 @@ const ObservableStore = require('obs-store') const extend = require('xtend') +const log = require('loglevel') /** * @typedef {Object} InitState @@ -9,11 +10,12 @@ const extend = require('xtend') /** * @typedef {Object} OnboardingOptions * @property {InitState} initState The initial controller state + * @property {PreferencesController} preferencesController Controller for managing user perferences */ /** * Controller responsible for maintaining - * a cache of account balances in local storage + * state related to onboarding */ class OnboardingController { /** @@ -22,10 +24,28 @@ class OnboardingController { * @param {OnboardingOptions} [opts] Controller configuration parameters */ constructor (opts = {}) { - const initState = extend({ - seedPhraseBackedUp: true, - }, opts.initState) + const initialTransientState = { + onboardingTabs: {}, + } + const initState = extend( + { + seedPhraseBackedUp: true, + }, + opts.initState, + initialTransientState, + ) this.store = new ObservableStore(initState) + this.preferencesController = opts.preferencesController + this.completedOnboarding = this.preferencesController.store.getState().completedOnboarding + + this.preferencesController.store.subscribe(({ completedOnboarding }) => { + if (completedOnboarding !== this.completedOnboarding) { + this.completedOnboarding = completedOnboarding + if (completedOnboarding) { + this.store.updateState(initialTransientState) + } + } + }) } setSeedPhraseBackedUp (newSeedPhraseBackUpState) { @@ -38,6 +58,24 @@ class OnboardingController { return this.store.getState().seedPhraseBackedUp } + /** + * Registering a site as having initiated onboarding + * + * @param {string} location - The location of the site registering + * @param {string} tabId - The id of the tab registering + */ + async registerOnboarding (location, tabId) { + if (this.completedOnboarding) { + log.debug('Ignoring registerOnboarding; user already onboarded') + return + } + const onboardingTabs = Object.assign({}, this.store.getState().onboardingTabs) + if (!onboardingTabs[location] || onboardingTabs[location] !== tabId) { + log.debug(`Registering onboarding tab at location '${location}' with tabId '${tabId}'`) + onboardingTabs[location] = tabId + this.store.updateState({ onboardingTabs }) + } + } } module.exports = OnboardingController diff --git a/app/scripts/controllers/permissions/index.js b/app/scripts/controllers/permissions/index.js new file mode 100644 index 000000000..899044b13 --- /dev/null +++ b/app/scripts/controllers/permissions/index.js @@ -0,0 +1,377 @@ +const JsonRpcEngine = require('json-rpc-engine') +const asMiddleware = require('json-rpc-engine/src/asMiddleware') +const ObservableStore = require('obs-store') +const RpcCap = require('rpc-cap').CapabilitiesController +const { ethErrors } = require('eth-json-rpc-errors') + +const getRestrictedMethods = require('./restrictedMethods') +const createMethodMiddleware = require('./methodMiddleware') +const createLoggerMiddleware = require('./loggerMiddleware') + +// Methods that do not require any permissions to use: +const SAFE_METHODS = require('./permissions-safe-methods.json') + +// some constants +const METADATA_STORE_KEY = 'domainMetadata' +const LOG_STORE_KEY = 'permissionsLog' +const HISTORY_STORE_KEY = 'permissionsHistory' +const WALLET_METHOD_PREFIX = 'wallet_' +const CAVEAT_NAMES = { + exposedAccounts: 'exposedAccounts', +} +const ACCOUNTS_CHANGED_NOTIFICATION = 'wallet_accountsChanged' + +class PermissionsController { + + constructor ( + { + platform, notifyDomain, notifyAllDomains, keyringController, + } = {}, + restoredPermissions = {}, + restoredState = {}) { + this.store = new ObservableStore({ + [METADATA_STORE_KEY]: restoredState[METADATA_STORE_KEY] || {}, + [LOG_STORE_KEY]: restoredState[LOG_STORE_KEY] || [], + [HISTORY_STORE_KEY]: restoredState[HISTORY_STORE_KEY] || {}, + }) + this.notifyDomain = notifyDomain + this.notifyAllDomains = notifyAllDomains + this.keyringController = keyringController + this._platform = platform + this._restrictedMethods = getRestrictedMethods(this) + this._initializePermissions(restoredPermissions) + } + + createMiddleware ({ origin, extensionId }) { + + if (extensionId) { + this.store.updateState({ + [METADATA_STORE_KEY]: { + ...this.store.getState()[METADATA_STORE_KEY], + [origin]: { extensionId }, + }, + }) + } + + const engine = new JsonRpcEngine() + + engine.push(createLoggerMiddleware({ + walletPrefix: WALLET_METHOD_PREFIX, + restrictedMethods: Object.keys(this._restrictedMethods), + ignoreMethods: [ 'wallet_sendDomainMetadata' ], + store: this.store, + logStoreKey: LOG_STORE_KEY, + historyStoreKey: HISTORY_STORE_KEY, + })) + + engine.push(createMethodMiddleware({ + store: this.store, + storeKey: METADATA_STORE_KEY, + getAccounts: this.getAccounts.bind(this, origin), + requestAccountsPermission: this._requestPermissions.bind( + this, origin, { eth_accounts: {} } + ), + })) + + engine.push(this.permissions.providerMiddlewareFunction.bind( + this.permissions, { origin } + )) + return asMiddleware(engine) + } + + /** + * Returns the accounts that should be exposed for the given origin domain, + * if any. This method exists for when a trusted context needs to know + * which accounts are exposed to a given domain. + * + * @param {string} origin - The origin string. + */ + getAccounts (origin) { + return new Promise((resolve, _) => { + + const req = { method: 'eth_accounts' } + const res = {} + this.permissions.providerMiddlewareFunction( + { origin }, req, res, () => {}, _end + ) + + function _end () { + if (res.error || !Array.isArray(res.result)) { + resolve([]) + } else { + resolve(res.result) + } + } + }) + } + + /** + * Submits a permissions request to rpc-cap. Internal use only. + * + * @param {string} origin - The origin string. + * @param {IRequestedPermissions} permissions - The requested permissions. + */ + _requestPermissions (origin, permissions) { + return new Promise((resolve, reject) => { + + const req = { method: 'wallet_requestPermissions', params: [permissions] } + const res = {} + this.permissions.providerMiddlewareFunction( + { origin }, req, res, () => {}, _end + ) + + function _end (err) { + if (err || res.error) { + reject(err || res.error) + } else { + resolve(res.result) + } + } + }) + } + + /** + * User approval callback. The request can fail if the request is invalid. + * + * @param {object} approved the approved request object + */ + async approvePermissionsRequest (approved, accounts) { + + const { id } = approved.metadata + const approval = this.pendingApprovals[id] + + try { + + // attempt to finalize the request and resolve it + await this.finalizePermissionsRequest(approved.permissions, accounts) + approval.resolve(approved.permissions) + + } catch (err) { + + // if finalization fails, reject the request + approval.reject(ethErrors.rpc.invalidRequest({ + message: err.message, data: err, + })) + } + + delete this.pendingApprovals[id] + } + + /** + * User rejection callback. + * + * @param {string} id the id of the rejected request + */ + async rejectPermissionsRequest (id) { + const approval = this.pendingApprovals[id] + approval.reject(ethErrors.provider.userRejectedRequest()) + delete this.pendingApprovals[id] + } + + /** + * Grants the given origin the eth_accounts permission for the given account(s). + * This method should ONLY be called as a result of direct user action in the UI, + * with the intention of supporting legacy dapps that don't support EIP 1102. + * + * @param {string} origin - The origin to expose the account(s) to. + * @param {Array} accounts - The account(s) to expose. + */ + async legacyExposeAccounts (origin, accounts) { + + const permissions = { + eth_accounts: {}, + } + + await this.finalizePermissionsRequest(permissions, accounts) + + let error + try { + await new Promise((resolve, reject) => { + this.permissions.grantNewPermissions(origin, permissions, {}, err => err ? resolve() : reject(err)) + }) + } catch (err) { + error = err + } + + if (error) { + if (error.code === 4001) { + throw error + } else { + throw ethErrors.rpc.internal({ + message: `Failed to add 'eth_accounts' to '${origin}'.`, + data: { + originalError: error, + accounts, + }, + }) + } + } + } + + /** + * Update the accounts exposed to the given origin. + * Throws error if the update fails. + * + * @param {string} origin - The origin to change the exposed accounts for. + * @param {string[]} accounts - The new account(s) to expose. + */ + async updateExposedAccounts (origin, accounts) { + + await this.validateExposedAccounts(accounts) + + this.permissions.updateCaveatFor( + origin, 'eth_accounts', CAVEAT_NAMES.exposedAccounts, accounts + ) + + this.notifyDomain(origin, { + method: ACCOUNTS_CHANGED_NOTIFICATION, + result: accounts, + }) + } + + /** + * Finalizes a permissions request. + * Throws if request validation fails. + * + * @param {Object} requestedPermissions - The requested permissions. + * @param {string[]} accounts - The accounts to expose, if any. + */ + async finalizePermissionsRequest (requestedPermissions, accounts) { + + const { eth_accounts: ethAccounts } = requestedPermissions + + if (ethAccounts) { + + await this.validateExposedAccounts(accounts) + + if (!ethAccounts.caveats) { + ethAccounts.caveats = [] + } + + // caveat names are unique, and we will only construct this caveat here + ethAccounts.caveats = ethAccounts.caveats.filter(c => ( + c.name !== CAVEAT_NAMES.exposedAccounts + )) + + ethAccounts.caveats.push( + { + type: 'filterResponse', + value: accounts, + name: CAVEAT_NAMES.exposedAccounts, + }, + ) + } + } + + /** + * Validate an array of accounts representing accounts to be exposed + * to a domain. Throws error if validation fails. + * + * @param {string[]} accounts - An array of addresses. + */ + async validateExposedAccounts (accounts) { + + if (!Array.isArray(accounts) || accounts.length === 0) { + throw new Error('Must provide non-empty array of account(s).') + } + + // assert accounts exist + const allAccounts = await this.keyringController.getAccounts() + accounts.forEach(acc => { + if (!allAccounts.includes(acc)) { + throw new Error(`Unknown account: ${acc}`) + } + }) + } + + /** + * Removes the given permissions for the given domain. + * @param {object} domains { origin: [permissions] } + */ + removePermissionsFor (domains) { + + Object.entries(domains).forEach(([origin, perms]) => { + + this.permissions.removePermissionsFor( + origin, + perms.map(methodName => { + + if (methodName === 'eth_accounts') { + this.notifyDomain( + origin, + { method: ACCOUNTS_CHANGED_NOTIFICATION, result: [] } + ) + } + + return { parentCapability: methodName } + }) + ) + }) + } + + /** + * Removes all known domains and their related permissions. + */ + clearPermissions () { + this.permissions.clearDomains() + this.notifyAllDomains({ + method: ACCOUNTS_CHANGED_NOTIFICATION, + result: [], + }) + } + + /** + * A convenience method for retrieving a login object + * or creating a new one if needed. + * + * @param {string} origin = The origin string representing the domain. + */ + _initializePermissions (restoredState) { + + // these permission requests are almost certainly stale + const initState = { ...restoredState, permissionsRequests: [] } + + this.pendingApprovals = {} + + this.permissions = new RpcCap({ + + // Supports passthrough methods: + safeMethods: SAFE_METHODS, + + // optional prefix for internal methods + methodPrefix: WALLET_METHOD_PREFIX, + + restrictedMethods: this._restrictedMethods, + + /** + * A promise-returning callback used to determine whether to approve + * permissions requests or not. + * + * Currently only returns a boolean, but eventually should return any + * specific parameters or amendments to the permissions. + * + * @param {string} req - The internal rpc-cap user request object. + */ + requestUserApproval: async (req) => { + const { metadata: { id } } = req + + this._platform.openExtensionInBrowser('connect') + + return new Promise((resolve, reject) => { + this.pendingApprovals[id] = { resolve, reject } + }) + }, + }, initState) + } +} + +module.exports = { + PermissionsController, + addInternalMethodPrefix: prefix, + CAVEAT_NAMES, +} + + +function prefix (method) { + return WALLET_METHOD_PREFIX + method +} diff --git a/app/scripts/controllers/permissions/loggerMiddleware.js b/app/scripts/controllers/permissions/loggerMiddleware.js new file mode 100644 index 000000000..0e3c7f393 --- /dev/null +++ b/app/scripts/controllers/permissions/loggerMiddleware.js @@ -0,0 +1,169 @@ + +const clone = require('clone') +const { isValidAddress } = require('ethereumjs-util') + +const LOG_LIMIT = 100 + +/** + * Create middleware for logging requests and responses to restricted and + * permissions-related methods. + */ +module.exports = function createLoggerMiddleware ({ + walletPrefix, restrictedMethods, store, logStoreKey, historyStoreKey, ignoreMethods, +}) { + return (req, res, next, _end) => { + let activityEntry, requestedMethods + const { origin, method } = req + const isInternal = method.startsWith(walletPrefix) + if ((isInternal || restrictedMethods.includes(method)) && !ignoreMethods.includes(method)) { + activityEntry = logActivity(req, isInternal) + if (method === `${walletPrefix}requestPermissions`) { + requestedMethods = getRequestedMethods(req) + } + } else if (method === 'eth_requestAccounts') { + activityEntry = logActivity(req, isInternal) + requestedMethods = [ 'eth_accounts' ] + } else { + return next() + } + + next(cb => { + const time = Date.now() + addResponse(activityEntry, res, time) + if (!res.error && requestedMethods) { + logHistory(requestedMethods, origin, res.result, time, method === 'eth_requestAccounts') + } + cb() + }) + } + + function logActivity (request, isInternal) { + const activityEntry = { + id: request.id, + method: request.method, + methodType: isInternal ? 'internal' : 'restricted', + origin: request.origin, + request: cloneObj(request), + requestTime: Date.now(), + response: null, + responseTime: null, + success: null, + } + commitActivity(activityEntry) + return activityEntry + } + + function addResponse (activityEntry, response, time) { + if (!response) { + return + } + activityEntry.response = cloneObj(response) + activityEntry.responseTime = time + activityEntry.success = !response.error + } + + function commitActivity (entry) { + const logs = store.getState()[logStoreKey] + if (logs.length > LOG_LIMIT - 2) { + logs.pop() + } + logs.push(entry) + store.updateState({ [logStoreKey]: logs }) + } + + function getRequestedMethods (request) { + if ( + !request.params || + typeof request.params[0] !== 'object' || + Array.isArray(request.params[0]) + ) { + return null + } + return Object.keys(request.params[0]) + } + + function logHistory (requestedMethods, origin, result, time, isEthRequestAccounts) { + let accounts, entries + if (isEthRequestAccounts) { + accounts = result + const accountToTimeMap = accounts.reduce((acc, account) => ({ ...acc, [account]: time }), {}) + entries = { 'eth_accounts': { accounts: accountToTimeMap, lastApproved: time } } + } else { + entries = result + ? result + .map(perm => { + if (perm.parentCapability === 'eth_accounts') { + accounts = getAccountsFromPermission(perm) + } + return perm.parentCapability + }) + .reduce((acc, m) => { + if (requestedMethods.includes(m)) { + if (m === 'eth_accounts') { + const accountToTimeMap = accounts.reduce((acc, account) => ({ ...acc, [account]: time }), {}) + acc[m] = { lastApproved: time, accounts: accountToTimeMap } + } else { + acc[m] = { lastApproved: time } + } + } + return acc + }, {}) + : {} + } + + if (Object.keys(entries).length > 0) { + commitHistory(origin, entries) + } + } + + function commitHistory (origin, entries) { + const history = store.getState()[historyStoreKey] || {} + const newOriginHistory = { + ...history[origin], + ...entries, + } + + if (history[origin] && history[origin]['eth_accounts'] && entries['eth_accounts']) { + newOriginHistory['eth_accounts'] = { + lastApproved: entries['eth_accounts'].lastApproved, + accounts: { + ...history[origin]['eth_accounts'].accounts, + ...entries['eth_accounts'].accounts, + }, + } + } + + history[origin] = newOriginHistory + + store.updateState({ [historyStoreKey]: history }) + } +} + +// the call to clone is set to disallow circular references +// we attempt cloning at a depth of 3 and 2, then return a +// shallow copy of the object +function cloneObj (obj) { + for (let i = 3; i > 1; i--) { + try { + return clone(obj, false, i) + } catch (_) {} + } + return { ...obj } +} + +function getAccountsFromPermission (perm) { + if (perm.parentCapability !== 'eth_accounts' || !perm.caveats) { + return [] + } + const accounts = {} + for (const c of perm.caveats) { + if (c.type === 'filterResponse' && Array.isArray(c.value)) { + for (const v of c.value) { + if (isValidAddress(v)) { + accounts[v] = true + } + } + } + } + return Object.keys(accounts) +} diff --git a/app/scripts/controllers/permissions/methodMiddleware.js b/app/scripts/controllers/permissions/methodMiddleware.js new file mode 100644 index 000000000..e0b451c9f --- /dev/null +++ b/app/scripts/controllers/permissions/methodMiddleware.js @@ -0,0 +1,90 @@ + +const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware') +const { ethErrors } = require('eth-json-rpc-errors') + +/** + * Create middleware for handling certain methods and preprocessing permissions requests. + */ +module.exports = function createMethodMiddleware ({ + store, storeKey, getAccounts, requestAccountsPermission, +}) { + return createAsyncMiddleware(async (req, res, next) => { + + if (typeof req.method !== 'string') { + res.error = ethErrors.rpc.invalidRequest({ data: req}) + return + } + + switch (req.method) { + + // intercepting eth_accounts requests for backwards compatibility, + // i.e. return an empty array instead of an error + case 'eth_accounts': + + res.result = await getAccounts() + return + + case 'eth_requestAccounts': + + // first, just try to get accounts + let accounts = await getAccounts() + if (accounts.length > 0) { + res.result = accounts + return + } + + // if no accounts, request the accounts permission + try { + await requestAccountsPermission() + } catch (err) { + res.error = err + return + } + + // get the accounts again + accounts = await getAccounts() + if (accounts.length > 0) { + res.result = accounts + } else { + // this should never happen + res.error = ethErrors.rpc.internal( + 'Accounts unexpectedly unavailable. Please report this bug.' + ) + } + + return + + // custom method for getting metadata from the requesting domain + case 'wallet_sendDomainMetadata': + + const storeState = store.getState()[storeKey] + const extensionId = storeState[req.origin] + ? storeState[req.origin].extensionId + : undefined + + if ( + req.domainMetadata && + typeof req.domainMetadata.name === 'string' + ) { + + store.updateState({ + [storeKey]: { + ...storeState, + [req.origin]: { + extensionId, + ...req.domainMetadata, + }, + }, + }) + } + + res.result = true + return + + default: + break + } + + next() + }) +} diff --git a/app/scripts/controllers/permissions/permissions-safe-methods.json b/app/scripts/controllers/permissions/permissions-safe-methods.json new file mode 100644 index 000000000..17b46b531 --- /dev/null +++ b/app/scripts/controllers/permissions/permissions-safe-methods.json @@ -0,0 +1,49 @@ +[ + "web3_sha3", + "net_listening", + "net_peerCount", + "net_version", + "eth_blockNumber", + "eth_call", + "eth_chainId", + "eth_coinbase", + "eth_estimateGas", + "eth_gasPrice", + "eth_getBalance", + "eth_getBlockByHash", + "eth_getBlockByNumber", + "eth_getBlockTransactionCountByHash", + "eth_getBlockTransactionCountByNumber", + "eth_getCode", + "eth_getFilterChanges", + "eth_getFilterLogs", + "eth_getLogs", + "eth_getStorageAt", + "eth_getTransactionByBlockHashAndIndex", + "eth_getTransactionByBlockNumberAndIndex", + "eth_getTransactionByHash", + "eth_getTransactionCount", + "eth_getTransactionReceipt", + "eth_getUncleByBlockHashAndIndex", + "eth_getUncleByBlockNumberAndIndex", + "eth_getUncleCountByBlockHash", + "eth_getUncleCountByBlockNumber", + "eth_getWork", + "eth_hashrate", + "eth_mining", + "eth_newBlockFilter", + "eth_newFilter", + "eth_newPendingTransactionFilter", + "eth_protocolVersion", + "eth_sendRawTransaction", + "eth_sendTransaction", + "eth_sign", + "personal_sign", + "eth_signTypedData", + "eth_signTypedData_v1", + "eth_signTypedData_v3", + "eth_submitHashrate", + "eth_submitWork", + "eth_syncing", + "eth_uninstallFilter" +] \ No newline at end of file diff --git a/app/scripts/controllers/permissions/restrictedMethods.js b/app/scripts/controllers/permissions/restrictedMethods.js new file mode 100644 index 000000000..cd87b2d57 --- /dev/null +++ b/app/scripts/controllers/permissions/restrictedMethods.js @@ -0,0 +1,20 @@ + +module.exports = function getRestrictedMethods (permissionsController) { + return { + + 'eth_accounts': { + description: 'View the address of the selected account', + method: (_, res, __, end) => { + permissionsController.keyringController.getAccounts() + .then((accounts) => { + res.result = accounts + end() + }) + .catch((err) => { + res.error = err + end(err) + }) + }, + }, + } +} diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 1cfbb4d4c..409ce6876 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -1,4 +1,5 @@ const ObservableStore = require('obs-store') +const { addInternalMethodPrefix } = require('./permissions') const normalizeAddress = require('eth-sig-util').normalize const { isValidAddress, sha3, bufferToHex } = require('ethereumjs-util') const extend = require('xtend') @@ -57,7 +58,6 @@ class PreferencesController { useNativeCurrencyAsPrimaryCurrency: true, }, completedOnboarding: false, - migratedPrivacyMode: false, metaMetricsId: null, metaMetricsSendCount: 0, }, opts.initState) @@ -187,7 +187,10 @@ class PreferencesController { * @param {Function} - end */ async requestWatchAsset (req, res, next, end) { - if (req.method === 'metamask_watchAsset' || req.method === 'wallet_watchAsset') { + if ( + req.method === 'metamask_watchAsset' || + req.method === addInternalMethodPrefix('watchAsset') + ) { const { type, options } = req.params switch (type) { case 'ERC20': @@ -303,7 +306,9 @@ class PreferencesController { const accountTokens = this.store.getState().accountTokens addresses.forEach((address) => { // skip if already exists - if (identities[address]) return + if (identities[address]) { + return + } // add missing identity const identityCount = Object.keys(identities).length @@ -335,7 +340,9 @@ class PreferencesController { if (Object.keys(newlyLost).length > 0) { // Notify our servers: - if (this.diagnostics) this.diagnostics.reportOrphans(newlyLost) + if (this.diagnostics) { + this.diagnostics.reportOrphans(newlyLost) + } // store lost accounts for (const key in newlyLost) { @@ -463,7 +470,9 @@ class PreferencesController { * @return {Promise} */ setAccountLabel (account, label) { - if (!account) throw new Error('setAccountLabel requires a valid address, got ' + String(account)) + if (!account) { + throw new Error('setAccountLabel requires a valid address, got ' + String(account)) + } const address = normalizeAddress(account) const {identities} = this.store.getState() identities[address] = identities[address] || {} @@ -500,7 +509,9 @@ class PreferencesController { updateRpc (newRpcDetails) { const rpcList = this.getFrequentRpcListDetail() - const index = rpcList.findIndex((element) => { return element.rpcUrl === newRpcDetails.rpcUrl }) + const index = rpcList.findIndex((element) => { + return element.rpcUrl === newRpcDetails.rpcUrl + }) if (index > -1) { const rpcDetail = rpcList[index] const updatedRpc = extend(rpcDetail, newRpcDetails) @@ -524,7 +535,9 @@ class PreferencesController { */ addToFrequentRpcList (url, chainId, ticker = 'ETH', nickname = '', rpcPrefs = {}) { const rpcList = this.getFrequentRpcListDetail() - const index = rpcList.findIndex((element) => { return element.rpcUrl === url }) + const index = rpcList.findIndex((element) => { + return element.rpcUrl === url + }) if (index !== -1) { rpcList.splice(index, 1) } @@ -548,7 +561,9 @@ class PreferencesController { */ removeFromFrequentRpcList (url) { const rpcList = this.getFrequentRpcListDetail() - const index = rpcList.findIndex((element) => { return element.rpcUrl === url }) + const index = rpcList.findIndex((element) => { + return element.rpcUrl === url + }) if (index !== -1) { rpcList.splice(index, 1) } @@ -632,13 +647,6 @@ class PreferencesController { return Promise.resolve(true) } - unsetMigratedPrivacyMode () { - this.store.updateState({ - migratedPrivacyMode: false, - }) - return Promise.resolve() - } - // // PRIVATE METHODS // @@ -687,10 +695,16 @@ class PreferencesController { */ _getTokenRelatedStates (selectedAddress) { const accountTokens = this.store.getState().accountTokens - if (!selectedAddress) selectedAddress = this.store.getState().selectedAddress + if (!selectedAddress) { + selectedAddress = this.store.getState().selectedAddress + } const providerType = this.network.providerStore.getState().type - if (!(selectedAddress in accountTokens)) accountTokens[selectedAddress] = {} - if (!(providerType in accountTokens[selectedAddress])) accountTokens[selectedAddress][providerType] = [] + if (!(selectedAddress in accountTokens)) { + accountTokens[selectedAddress] = {} + } + if (!(providerType in accountTokens[selectedAddress])) { + accountTokens[selectedAddress][providerType] = [] + } const tokens = accountTokens[selectedAddress][providerType] return { tokens, accountTokens, providerType, selectedAddress } } @@ -727,13 +741,19 @@ class PreferencesController { */ _validateERC20AssetParams (opts) { const { rawAddress, symbol, decimals } = opts - if (!rawAddress || !symbol || typeof decimals === 'undefined') throw new Error(`Cannot suggest token without address, symbol, and decimals`) - if (!(symbol.length < 7)) throw new Error(`Invalid symbol ${symbol} more than six characters`) + if (!rawAddress || !symbol || typeof decimals === 'undefined') { + throw new Error(`Cannot suggest token without address, symbol, and decimals`) + } + if (!(symbol.length < 7)) { + throw new Error(`Invalid symbol ${symbol} more than six characters`) + } const numDecimals = parseInt(decimals, 10) if (isNaN(numDecimals) || numDecimals > 36 || numDecimals < 0) { throw new Error(`Invalid decimals ${decimals} must be at least 0, and not over 36`) } - if (!isValidAddress(rawAddress)) throw new Error(`Invalid address ${rawAddress}`) + if (!isValidAddress(rawAddress)) { + throw new Error(`Invalid address ${rawAddress}`) + } } } diff --git a/app/scripts/controllers/provider-approval.js b/app/scripts/controllers/provider-approval.js deleted file mode 100644 index 00ff626f7..000000000 --- a/app/scripts/controllers/provider-approval.js +++ /dev/null @@ -1,175 +0,0 @@ -const ObservableStore = require('obs-store') -const SafeEventEmitter = require('safe-event-emitter') -const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware') -const { errors: rpcErrors } = require('eth-json-rpc-errors') - -/** - * A controller that services user-approved requests for a full Ethereum provider API - */ -class ProviderApprovalController extends SafeEventEmitter { - /** - * Creates a ProviderApprovalController - * - * @param {Object} [config] - Options to configure controller - */ - constructor ({ closePopup, initState, keyringController, openPopup, preferencesController } = {}) { - super() - this.closePopup = closePopup - this.keyringController = keyringController - this.openPopup = openPopup - this.preferencesController = preferencesController - this.memStore = new ObservableStore({ - providerRequests: [], - }) - - const defaultState = { approvedOrigins: {} } - this.store = new ObservableStore(Object.assign(defaultState, initState)) - } - - /** - * 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 ({ senderUrl, extensionId, 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 - const origin = senderUrl.hostname - if (this.shouldExposeAccounts(origin) && isUnlocked) { - res.result = [this.preferencesController.getSelectedAddress()] - return - } - // register the provider request - const metadata = { hostname: senderUrl.hostname, origin } - if (extensionId) { - metadata.extensionId = extensionId - } else { - const siteMetadata = await getSiteMetadata(origin) - Object.assign(metadata, { siteTitle: siteMetadata.name, siteImage: siteMetadata.icon}) - } - this._handleProviderRequest(metadata) - // 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 rpcErrors.eth.userRejectedRequest('User denied account authorization') - } - }) - } - - /** - * @typedef {Object} SiteMetadata - * @param {string} hostname - The hostname of the site - * @param {string} origin - The origin of the site - * @param {string} [siteTitle] - The title of the site - * @param {string} [siteImage] - The icon for the site - * @param {string} [extensionId] - The extension ID of the extension - */ - /** - * Called when a tab requests access to a full Ethereum provider API - * - * @param {SiteMetadata} siteMetadata - The metadata for the site requesting full provider access - */ - _handleProviderRequest (siteMetadata) { - const { providerRequests } = this.memStore.getState() - const origin = siteMetadata.origin - this.memStore.updateState({ - providerRequests: [ - ...providerRequests, - siteMetadata, - ], - }) - const isUnlocked = this.keyringController.memStore.getState().isUnlocked - const { approvedOrigins } = this.store.getState() - const originAlreadyHandled = approvedOrigins[origin] - if (originAlreadyHandled && isUnlocked) { - return - } - this.openPopup && this.openPopup() - } - - /** - * Called when a user approves access to a full Ethereum provider API - * - * @param {string} origin - origin of the domain that had provider access approved - */ - approveProviderRequestByOrigin (origin) { - if (this.closePopup) { - this.closePopup() - } - - const { approvedOrigins } = this.store.getState() - const { providerRequests } = this.memStore.getState() - const providerRequest = providerRequests.find((request) => request.origin === origin) - const remainingProviderRequests = providerRequests.filter(request => request.origin !== origin) - this.store.updateState({ - approvedOrigins: { - ...approvedOrigins, - [origin]: { - siteTitle: providerRequest ? providerRequest.siteTitle : null, - siteImage: providerRequest ? providerRequest.siteImage : null, - hostname: providerRequest ? providerRequest.hostname : null, - }, - }, - }) - this.memStore.updateState({ providerRequests: remainingProviderRequests }) - this.emit(`resolvedRequest:${origin}`, { approved: true }) - } - - /** - * Called when a tab rejects access to a full Ethereum provider API - * - * @param {string} origin - origin of the domain that had provider access approved - */ - rejectProviderRequestByOrigin (origin) { - if (this.closePopup) { - this.closePopup() - } - - const { approvedOrigins } = this.store.getState() - const { providerRequests } = this.memStore.getState() - const remainingProviderRequests = providerRequests.filter(request => request.origin !== origin) - - // We're cloning and deleting keys here because we don't want to keep unneeded keys - const _approvedOrigins = Object.assign({}, approvedOrigins) - delete _approvedOrigins[origin] - - this.store.putState({ approvedOrigins: _approvedOrigins }) - this.memStore.putState({ providerRequests: remainingProviderRequests }) - this.emit(`resolvedRequest:${origin}`, { approved: false }) - } - - /** - * Clears any approvals for user-approved origins - */ - clearApprovedOrigins () { - this.store.updateState({ - approvedOrigins: {}, - }) - } - - /** - * Determines if a given origin should have accounts exposed - * - * @param {string} origin - Domain origin to check for approval status - * @returns {boolean} - True if the origin has been approved - */ - shouldExposeAccounts (origin) { - return Boolean(this.store.getState().approvedOrigins[origin]) - } - - /** - * Returns a merged state representation - * @return {object} - * @private - */ - _getMergedState () { - return Object.assign({}, this.memStore.getState(), this.store.getState()) - } -} - -module.exports = ProviderApprovalController diff --git a/app/scripts/controllers/recent-blocks.js b/app/scripts/controllers/recent-blocks.js index a2b5d1bae..9e5a384a9 100644 --- a/app/scripts/controllers/recent-blocks.js +++ b/app/scripts/controllers/recent-blocks.js @@ -90,7 +90,9 @@ class RecentBlocksController { async processBlock (newBlockNumberHex) { const newBlockNumber = Number.parseInt(newBlockNumberHex, 16) const newBlock = await this.getBlockByNumber(newBlockNumber, true) - if (!newBlock) return + if (!newBlock) { + return + } const block = this.mapTransactionsToPrices(newBlock) @@ -162,7 +164,9 @@ class RecentBlocksController { await Promise.all(targetBlockNumbers.map(async (targetBlockNumber) => { try { const newBlock = await this.getBlockByNumber(targetBlockNumber, true) - if (!newBlock) return + if (!newBlock) { + return + } this.backfillBlock(newBlock) } catch (e) { diff --git a/app/scripts/controllers/threebox.js b/app/scripts/controllers/threebox.js index 5bcab29ed..8226fb6b1 100644 --- a/app/scripts/controllers/threebox.js +++ b/app/scripts/controllers/threebox.js @@ -28,7 +28,9 @@ class ThreeBoxController { this.provider = this._createProvider({ version, getAccounts: async ({ origin }) => { - if (origin !== '3Box') { return [] } + if (origin !== '3Box') { + return [] + } const isUnlocked = getKeyringControllerState().isUnlocked const accounts = await this.keyringController.getAccounts() diff --git a/app/scripts/controllers/token-rates.js b/app/scripts/controllers/token-rates.js index 9b86a9ebf..7c8526c34 100644 --- a/app/scripts/controllers/token-rates.js +++ b/app/scripts/controllers/token-rates.js @@ -28,7 +28,9 @@ class TokenRatesController { * Updates exchange rates for all tokens */ async updateExchangeRates () { - if (!this.isActive) { return } + if (!this.isActive) { + return + } const contractExchangeRates = {} const nativeCurrency = this.currency ? this.currency.state.nativeCurrency.toLowerCase() : 'eth' const pairs = this._tokens.map(token => token.address).join(',') @@ -53,8 +55,12 @@ class TokenRatesController { */ set interval (interval) { this._handle && clearInterval(this._handle) - if (!interval) { return } - this._handle = setInterval(() => { this.updateExchangeRates() }, interval) + if (!interval) { + return + } + this._handle = setInterval(() => { + this.updateExchangeRates() + }, interval) } /** @@ -62,10 +68,14 @@ class TokenRatesController { */ set preferences (preferences) { this._preferences && this._preferences.unsubscribe() - if (!preferences) { return } + if (!preferences) { + return + } this._preferences = preferences this.tokens = preferences.getState().tokens - preferences.subscribe(({ tokens = [] }) => { this.tokens = tokens }) + preferences.subscribe(({ tokens = [] }) => { + this.tokens = tokens + }) } /** diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index 808c224c3..01d5d9781 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -3,7 +3,7 @@ const ObservableStore = require('obs-store') const ethUtil = require('ethereumjs-util') const Transaction = require('ethereumjs-tx') const EthQuery = require('ethjs-query') -const { errors: rpcErrors } = require('eth-json-rpc-errors') +const { ethErrors } = require('eth-json-rpc-errors') const abi = require('human-standard-token-abi') const abiDecoder = require('abi-decoder') abiDecoder.addABI(abi) @@ -54,6 +54,7 @@ const { hexToBn, bnToHex, BnMultiplyByFraction } = require('../../lib/util') @param {Object} opts.blockTracker - An instance of eth-blocktracker @param {Object} opts.provider - A network provider. @param {Function} opts.signTransaction - function the signs an ethereumjs-tx + @param {object} opts.getPermittedAccounts - get accounts that an origin has permissions for @param {Function} [opts.getGasPrice] - optional gas price calculator @param {Function} opts.signTransaction - ethTx signer that returns a rawTx @param {Number} [opts.txHistoryLimit] - number *optional* for limiting how many transactions are in state @@ -66,6 +67,7 @@ class TransactionController extends EventEmitter { this.networkStore = opts.networkStore || new ObservableStore({}) this.preferencesStore = opts.preferencesStore || new ObservableStore({}) this.provider = opts.provider + this.getPermittedAccounts = opts.getPermittedAccounts this.blockTracker = opts.blockTracker this.signEthTx = opts.signTransaction this.getGasPrice = opts.getGasPrice @@ -133,7 +135,7 @@ class TransactionController extends EventEmitter { /** Adds a tx to the txlist @emits ${txMeta.id}:unapproved -*/ + */ addTx (txMeta) { this.txStateManager.addTx(txMeta) this.emit(`${txMeta.id}:unapproved`, txMeta) @@ -148,18 +150,18 @@ class TransactionController extends EventEmitter { } /** - add a new unapproved transaction to the pipeline - - @returns {Promise} the hash of the transaction after being submitted to the network - @param txParams {object} - txParams for the transaction - @param opts {object} - with the key origin to put the origin on the txMeta + * Add a new unapproved transaction to the pipeline + * + * @returns {Promise} the hash of the transaction after being submitted to the network + * @param txParams {object} - txParams for the transaction + * @param opts {object} - with the key origin to put the origin on the txMeta */ - async newUnapprovedTransaction (txParams, opts = {}) { + log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`) - const initialTxMeta = await this.addUnapprovedTransaction(txParams) - initialTxMeta.origin = opts.origin - this.txStateManager.updateTx(initialTxMeta, '#newUnapprovedTransaction - adding the origin') + + const initialTxMeta = await this.addUnapprovedTransaction(txParams, opts.origin) + // listen for tx completion (success, fail) return new Promise((resolve, reject) => { this.txStateManager.once(`${initialTxMeta.id}:finished`, (finishedTxMeta) => { @@ -167,30 +169,27 @@ class TransactionController extends EventEmitter { case 'submitted': return resolve(finishedTxMeta.hash) case 'rejected': - return reject(cleanErrorStack(rpcErrors.eth.userRejectedRequest('MetaMask Tx Signature: User denied transaction signature.'))) + return reject(cleanErrorStack(ethErrors.provider.userRejectedRequest('MetaMask Tx Signature: User denied transaction signature.'))) case 'failed': - return reject(cleanErrorStack(rpcErrors.internal(finishedTxMeta.err.message))) + return reject(cleanErrorStack(ethErrors.rpc.internal(finishedTxMeta.err.message))) default: - return reject(cleanErrorStack(rpcErrors.internal(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(finishedTxMeta.txParams)}`))) + return reject(cleanErrorStack(ethErrors.rpc.internal(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(finishedTxMeta.txParams)}`))) } }) }) } /** - Validates and generates a txMeta with defaults and puts it in txStateManager - store + * Validates and generates a txMeta with defaults and puts it in txStateManager + * store. + * + * @returns {txMeta} + */ + async addUnapprovedTransaction (txParams, origin) { - @returns {txMeta} - */ - - async addUnapprovedTransaction (txParams) { // validate const normalizedTxParams = txUtils.normalizeTxParams(txParams) - // Assert the from address is the selected address - if (normalizedTxParams.from !== this.getSelectedAddress()) { - throw new Error(`Transaction from address isn't valid for this account`) - } + txUtils.validateTxParams(normalizedTxParams) /** `generateTxMeta` adds the default txMeta properties to the passed object. @@ -202,6 +201,30 @@ class TransactionController extends EventEmitter { txParams: normalizedTxParams, type: TRANSACTION_TYPE_STANDARD, }) + + if (origin === 'metamask') { + // Assert the from address is the selected address + if (normalizedTxParams.from !== this.getSelectedAddress()) { + throw ethErrors.rpc.internal({ + message: `Internally initiated transaction is using invalid account.`, + data: { + origin, + fromAddress: normalizedTxParams.from, + selectedAddress: this.getSelectedAddress(), + }, + }) + } + } else { + // Assert that the origin has permissions to initiate transactions from + // the specified address + const permittedAddresses = await this.getPermittedAccounts(origin) + if (!permittedAddresses.includes(normalizedTxParams.from)) { + throw ethErrors.provider.unauthorized({ data: { origin }}) + } + } + + txMeta['origin'] = origin + const { transactionCategory, getCodeResponse } = await this._determineTransactionCategory(txParams) txMeta.transactionCategory = transactionCategory this.addTx(txMeta) @@ -222,15 +245,16 @@ class TransactionController extends EventEmitter { txMeta.loadingDefaults = false // save txMeta - this.txStateManager.updateTx(txMeta) + this.txStateManager.updateTx(txMeta, 'Added new unapproved transaction.') return txMeta } + /** - adds the tx gas defaults: gas && gasPrice - @param txMeta {Object} - the txMeta object - @returns {Promise} resolves with txMeta -*/ + * Adds the tx gas defaults: gas && gasPrice + * @param txMeta {Object} - the txMeta object + * @returns {Promise} resolves with txMeta + */ async addTxGasDefaults (txMeta, getCodeResponse) { const txParams = txMeta.txParams // ensure value @@ -421,13 +445,16 @@ class TransactionController extends EventEmitter { log.error(err) } // must set transaction to submitted/failed before releasing lock - if (nonceLock) nonceLock.releaseLock() + if (nonceLock) { + nonceLock.releaseLock() + } // continue with error chain throw err } finally { this.inProcessOfSigning.delete(txId) } } + /** adds the chain id and signs the transaction and set the status to signed @param txId {number} - the tx's Id @@ -622,7 +649,9 @@ class TransactionController extends EventEmitter { } }) this.pendingTxTracker.on('tx:retry', (txMeta) => { - if (!('retryCount' in txMeta)) txMeta.retryCount = 0 + if (!('retryCount' in txMeta)) { + txMeta.retryCount = 0 + } txMeta.retryCount++ this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:retry') }) @@ -676,10 +705,14 @@ class TransactionController extends EventEmitter { const txMeta = this.txStateManager.getTx(txId) const { nonce, from } = txMeta.txParams const sameNonceTxs = this.txStateManager.getFilteredTxList({nonce, from}) - if (!sameNonceTxs.length) return + if (!sameNonceTxs.length) { + return + } // mark all same nonce transactions as dropped and give i a replacedBy hash sameNonceTxs.forEach((otherTxMeta) => { - if (otherTxMeta.id === txId) return + if (otherTxMeta.id === txId) { + return + } otherTxMeta.replacedBy = txMeta.hash this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:confirmed reference to confirmed txHash with same nonce') this.txStateManager.setTxStatusDropped(otherTxMeta.id) diff --git a/app/scripts/controllers/transactions/lib/tx-state-history-helper.js b/app/scripts/controllers/transactions/lib/tx-state-history-helper.js index 20e64696b..bf6e19f12 100644 --- a/app/scripts/controllers/transactions/lib/tx-state-history-helper.js +++ b/app/scripts/controllers/transactions/lib/tx-state-history-helper.js @@ -18,7 +18,9 @@ function migrateFromSnapshotsToDiffs (longHistory) { longHistory // convert non-initial history entries into diffs .map((entry, index) => { - if (index === 0) return entry + if (index === 0) { + return entry + } return generateHistoryEntry(longHistory[index - 1], entry) }) ) @@ -40,7 +42,9 @@ function generateHistoryEntry (previousState, newState, note) { const entry = jsonDiffer.compare(previousState, newState) // Add a note to the first op, since it breaks if we append it to the entry if (entry[0]) { - if (note) entry[0].note = note + if (note) { + entry[0].note = note + } entry[0].timestamp = Date.now() } diff --git a/app/scripts/controllers/transactions/lib/util.js b/app/scripts/controllers/transactions/lib/util.js index 0d2ddddef..86924e7fa 100644 --- a/app/scripts/controllers/transactions/lib/util.js +++ b/app/scripts/controllers/transactions/lib/util.js @@ -35,7 +35,9 @@ function normalizeTxParams (txParams, LowerCase) { // apply only keys in the normalizers const normalizedTxParams = {} for (const key in normalizers) { - if (txParams[key]) normalizedTxParams[key] = normalizers[key](txParams[key], LowerCase) + if (txParams[key]) { + normalizedTxParams[key] = normalizers[key](txParams[key], LowerCase) + } } return normalizedTxParams } @@ -64,8 +66,12 @@ function validateTxParams (txParams) { @param txParams {object} */ function validateFrom (txParams) { - if (!(typeof txParams.from === 'string')) throw new Error(`Invalid from address ${txParams.from} not a string`) - if (!isValidAddress(txParams.from)) throw new Error('Invalid from address') + if (!(typeof txParams.from === 'string')) { + throw new Error(`Invalid from address ${txParams.from} not a string`) + } + if (!isValidAddress(txParams.from)) { + throw new Error('Invalid from address') + } } /** diff --git a/app/scripts/controllers/transactions/pending-tx-tracker.js b/app/scripts/controllers/transactions/pending-tx-tracker.js index 8f4076f45..4e2db5ead 100644 --- a/app/scripts/controllers/transactions/pending-tx-tracker.js +++ b/app/scripts/controllers/transactions/pending-tx-tracker.js @@ -56,7 +56,9 @@ class PendingTransactionTracker extends EventEmitter { resubmitPendingTxs (blockNumber) { const pending = this.getPendingTransactions() // only try resubmitting if their are transactions to resubmit - if (!pending.length) return + if (!pending.length) { + return + } pending.forEach((txMeta) => this._resubmitTx(txMeta, blockNumber).catch((err) => { /* Dont marked as failed if the error is a "known" transaction warning @@ -79,7 +81,9 @@ class PendingTransactionTracker extends EventEmitter { errorMessage.includes('nonce too low') ) // ignore resubmit warnings, return early - if (isKnownTx) return + if (isKnownTx) { + return + } // encountered real error - transition to error state txMeta.warning = { error: errorMessage, @@ -107,10 +111,14 @@ class PendingTransactionTracker extends EventEmitter { const retryCount = txMeta.retryCount || 0 // Exponential backoff to limit retries at publishing - if (txBlockDistance <= Math.pow(2, retryCount) - 1) return + if (txBlockDistance <= Math.pow(2, retryCount) - 1) { + return + } // Only auto-submit already-signed txs: - if (!('rawTx' in txMeta)) return this.approveTransaction(txMeta.id) + if (!('rawTx' in txMeta)) { + return this.approveTransaction(txMeta.id) + } const rawTx = txMeta.rawTx const txHash = await this.publishTransaction(rawTx) @@ -132,7 +140,9 @@ class PendingTransactionTracker extends EventEmitter { const txId = txMeta.id // Only check submitted txs - if (txMeta.status !== 'submitted') return + if (txMeta.status !== 'submitted') { + return + } // extra check in case there was an uncaught error during the // signature and submission process diff --git a/app/scripts/controllers/transactions/tx-gas-utils.js b/app/scripts/controllers/transactions/tx-gas-utils.js index 287fb6f44..517137f86 100644 --- a/app/scripts/controllers/transactions/tx-gas-utils.js +++ b/app/scripts/controllers/transactions/tx-gas-utils.js @@ -142,9 +142,13 @@ class TxGasUtil { const bufferedGasLimitBn = initialGasLimitBn.muln(1.5) // if initialGasLimit is above blockGasLimit, dont modify it - if (initialGasLimitBn.gt(upperGasLimitBn)) return bnToHex(initialGasLimitBn) + if (initialGasLimitBn.gt(upperGasLimitBn)) { + return bnToHex(initialGasLimitBn) + } // if bufferedGasLimit is below blockGasLimit, use bufferedGasLimit - if (bufferedGasLimitBn.lt(upperGasLimitBn)) return bnToHex(bufferedGasLimitBn) + if (bufferedGasLimitBn.lt(upperGasLimitBn)) { + return bnToHex(bufferedGasLimitBn) + } // otherwise use blockGasLimit return bnToHex(upperGasLimitBn) } diff --git a/app/scripts/controllers/transactions/tx-state-manager.js b/app/scripts/controllers/transactions/tx-state-manager.js index e2bbe8e39..3e0c46f35 100644 --- a/app/scripts/controllers/transactions/tx-state-manager.js +++ b/app/scripts/controllers/transactions/tx-state-manager.js @@ -45,7 +45,9 @@ class TransactionStateManager extends EventEmitter { */ generateTxMeta (opts) { const netId = this.getNetwork() - if (netId === 'loading') throw new Error('MetaMask is having trouble connecting to the network') + if (netId === 'loading') { + throw new Error('MetaMask is having trouble connecting to the network') + } return extend({ id: createId(), time: (new Date()).getTime(), @@ -89,7 +91,9 @@ class TransactionStateManager extends EventEmitter { */ getApprovedTransactions (address) { const opts = { status: 'approved' } - if (address) opts.from = address + if (address) { + opts.from = address + } return this.getFilteredTxList(opts) } @@ -100,7 +104,9 @@ class TransactionStateManager extends EventEmitter { */ getPendingTransactions (address) { const opts = { status: 'submitted' } - if (address) opts.from = address + if (address) { + opts.from = address + } return this.getFilteredTxList(opts) } @@ -111,7 +117,9 @@ class TransactionStateManager extends EventEmitter { */ getConfirmedTransactions (address) { const opts = { status: 'confirmed' } - if (address) opts.from = address + if (address) { + opts.from = address + } return this.getFilteredTxList(opts) } @@ -243,10 +251,14 @@ class TransactionStateManager extends EventEmitter { // validate types switch (key) { case 'chainId': - if (typeof value !== 'number' && typeof value !== 'string') throw new Error(`${key} in txParams is not a Number or hex string. got: (${value})`) + if (typeof value !== 'number' && typeof value !== 'string') { + throw new Error(`${key} in txParams is not a Number or hex string. got: (${value})`) + } break default: - if (typeof value !== 'string') throw new Error(`${key} in txParams is not a string. got: (${value})`) + if (typeof value !== 'string') { + throw new Error(`${key} in txParams is not a string. got: (${value})`) + } break } }) diff --git a/app/scripts/createStandardProvider.js b/app/scripts/createStandardProvider.js deleted file mode 100644 index 2059b9b3a..000000000 --- a/app/scripts/createStandardProvider.js +++ /dev/null @@ -1,73 +0,0 @@ -class StandardProvider { - _isConnected - _provider - - constructor (provider) { - this._provider = provider - this._subscribe() - // indicate that we've connected, mostly just for standard compliance - setTimeout(() => { - this._onConnect() - }) - } - - _onClose () { - if (this._isConnected === undefined || this._isConnected) { - this._provider.emit('close', { - code: 1011, - reason: 'Network connection error', - }) - } - this._isConnected = false - } - - _onConnect () { - !this._isConnected && this._provider.emit('connect') - this._isConnected = true - } - - _subscribe () { - this._provider.on('data', (error, { method, params }) => { - if (!error && method === 'eth_subscription') { - this._provider.emit('notification', params.result) - } - }) - } - - /** - * Initiate an RPC method call - * - * @param {string} method - RPC method name to call - * @param {string[]} params - Array of RPC method parameters - * @returns {Promise<*>} Promise resolving to the result if successful - */ - send (method, params = []) { - return new Promise((resolve, reject) => { - try { - this._provider.sendAsync({ id: 1, jsonrpc: '2.0', method, params }, (error, response) => { - error = error || response.error - error ? reject(error) : resolve(response) - }) - } catch (error) { - reject(error) - } - }) - } -} - -/** - * Converts a legacy provider into an EIP-1193-compliant standard provider - * @param {Object} provider - Legacy provider to convert - * @returns {Object} Standard provider - */ -export default function createStandardProvider (provider) { - const standardProvider = new StandardProvider(provider) - const sendLegacy = provider.send - provider.send = (methodOrPayload, callbackOrArgs) => { - if (typeof methodOrPayload === 'string' && !callbackOrArgs || Array.isArray(callbackOrArgs)) { - return standardProvider.send(methodOrPayload, callbackOrArgs) - } - return sendLegacy.call(provider, methodOrPayload, callbackOrArgs) - } - return provider -} diff --git a/app/scripts/edge-encryptor.js b/app/scripts/edge-encryptor.js index 012672ed2..d086a854d 100644 --- a/app/scripts/edge-encryptor.js +++ b/app/scripts/edge-encryptor.js @@ -14,17 +14,17 @@ class EdgeEncryptor { * @returns {Promise} Promise resolving to an object with ciphertext */ encrypt (password, dataObject) { - var salt = this._generateSalt() + const salt = this._generateSalt() return this._keyFromPassword(password, salt) .then(function (key) { - var data = JSON.stringify(dataObject) - var dataBuffer = Unibabel.utf8ToBuffer(data) - var vector = global.crypto.getRandomValues(new Uint8Array(16)) - var resultbuffer = asmcrypto.AES_GCM.encrypt(dataBuffer, key, vector) + const data = JSON.stringify(dataObject) + const dataBuffer = Unibabel.utf8ToBuffer(data) + const vector = global.crypto.getRandomValues(new Uint8Array(16)) + const resultbuffer = asmcrypto.AES_GCM.encrypt(dataBuffer, key, vector) - var buffer = new Uint8Array(resultbuffer) - var vectorStr = Unibabel.bufferToBase64(vector) - var vaultStr = Unibabel.bufferToBase64(buffer) + const buffer = new Uint8Array(resultbuffer) + const vectorStr = Unibabel.bufferToBase64(vector) + const vaultStr = Unibabel.bufferToBase64(buffer) return JSON.stringify({ data: vaultStr, iv: vectorStr, @@ -48,7 +48,7 @@ class EdgeEncryptor { const encryptedData = Unibabel.base64ToBuffer(payload.data) const vector = Unibabel.base64ToBuffer(payload.iv) return new Promise((resolve, reject) => { - var result + let result try { result = asmcrypto.AES_GCM.decrypt(encryptedData, key, vector) } catch (err) { @@ -72,12 +72,12 @@ class EdgeEncryptor { */ _keyFromPassword (password, salt) { - var passBuffer = Unibabel.utf8ToBuffer(password) - var saltBuffer = Unibabel.base64ToBuffer(salt) + const passBuffer = Unibabel.utf8ToBuffer(password) + const saltBuffer = Unibabel.base64ToBuffer(salt) const iterations = 10000 const length = 32 // SHA256 hash size return new Promise((resolve) => { - var key = asmcrypto.Pbkdf2HmacSha256(passBuffer, saltBuffer, iterations, length) + const key = asmcrypto.Pbkdf2HmacSha256(passBuffer, saltBuffer, iterations, length) resolve(key) }) } @@ -89,9 +89,9 @@ class EdgeEncryptor { * @returns {string} Randomized base64 encoded data */ _generateSalt (byteCount = 32) { - var view = new Uint8Array(byteCount) + const view = new Uint8Array(byteCount) global.crypto.getRandomValues(view) - var b64encoded = btoa(String.fromCharCode.apply(null, view)) + const b64encoded = btoa(String.fromCharCode.apply(null, view)) return b64encoded } } diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index ec88243a4..d81d8587b 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -1,6 +1,5 @@ /*global Web3*/ - // 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... @@ -32,14 +31,14 @@ const restoreContextAfterImports = () => { } cleanContextForImports() -require('web3/dist/web3.min.js') + const log = require('loglevel') const LocalMessageDuplexStream = require('post-message-stream') -const setupDappAutoReload = require('./lib/auto-reload.js') const MetamaskInpageProvider = require('metamask-inpage-provider') -const createStandardProvider = require('./createStandardProvider').default -let warned = false +// TODO:deprecate:2020-01-13 +require('web3/dist/web3.min.js') +const setupDappAutoReload = require('./lib/auto-reload.js') restoreContextAfterImports() @@ -61,89 +60,6 @@ const inpageProvider = new MetamaskInpageProvider(metamaskStream) // set a high max listener count to avoid unnecesary warnings inpageProvider.setMaxListeners(100) -let warnedOfAutoRefreshDeprecation = false -// augment the provider with its enable method -inpageProvider.enable = function ({ force } = {}) { - if ( - !warnedOfAutoRefreshDeprecation && - inpageProvider.autoRefreshOnNetworkChange - ) { - console.warn(`MetaMask: MetaMask will soon stop reloading pages on network change. -If you rely upon this behavior, add a 'networkChanged' event handler to trigger the reload manually: https://metamask.github.io/metamask-docs/API_Reference/Ethereum_Provider#ethereum.on(eventname%2C-callback) -Set 'ethereum.autoRefreshOnNetworkChange' to 'false' to silence this warning: https://metamask.github.io/metamask-docs/API_Reference/Ethereum_Provider#ethereum.autorefreshonnetworkchange' -`) - warnedOfAutoRefreshDeprecation = true - } - return new Promise((resolve, reject) => { - inpageProvider.sendAsync({ method: 'eth_requestAccounts', params: [force] }, (error, response) => { - if (error || response.error) { - reject(error || response.error) - } else { - resolve(response.result) - } - }) - }) -} - -// give the dapps control of a refresh they can toggle this off on the window.ethereum -// this will be default true so it does not break any old apps. -inpageProvider.autoRefreshOnNetworkChange = true - - -// publicConfig isn't populated until we get a message from background. -// Using this getter will ensure the state is available -const getPublicConfigWhenReady = async () => { - 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 -} - -// add metamask-specific convenience methods -inpageProvider._metamask = new Proxy({ - /** - * Synchronously determines if this domain is currently enabled, with a potential false negative if called to soon - * - * @returns {boolean} - returns true if this domain is currently enabled - */ - isEnabled: function () { - const { isEnabled } = inpageProvider.publicConfigStore.getState() - return Boolean(isEnabled) - }, - - /** - * Asynchronously determines if this domain is currently enabled - * - * @returns {Promise} - Promise resolving to true if this domain is currently enabled - */ - isApproved: async function () { - const { isEnabled } = await getPublicConfigWhenReady() - return Boolean(isEnabled) - }, - - /** - * Determines if MetaMask is unlocked by the user - * - * @returns {Promise} - Promise resolving to true if MetaMask is currently unlocked - */ - isUnlocked: async function () { - const { isUnlocked } = await getPublicConfigWhenReady() - return Boolean(isUnlocked) - }, -}, { - get: function (obj, prop) { - !warned && console.warn('Heads up! ethereum._metamask exposes methods that have ' + - 'not been standardized yet. This means that these methods may not be implemented ' + - 'in other dapp browsers and may be removed from MetaMask in the future.') - warned = true - return obj[prop] - }, -}) - // Work around for web3@1.0 deleting the bound `sendAsync` but not the unbound // `sendAsync` method on the prototype, causing `this` reference issues const proxiedInpageProvider = new Proxy(inpageProvider, { @@ -152,11 +68,11 @@ const proxiedInpageProvider = new Proxy(inpageProvider, { deleteProperty: () => true, }) -window.ethereum = createStandardProvider(proxiedInpageProvider) +// +// TODO:deprecate:2020-01-13 +// -// // setup web3 -// if (typeof window.web3 !== 'undefined') { throw new Error(`MetaMask detected another web3. @@ -172,15 +88,13 @@ web3.setProvider = function () { } log.debug('MetaMask - injected web3') -setupDappAutoReload(web3, inpageProvider.publicConfigStore) +proxiedInpageProvider._web3Ref = web3.eth -// set web3 defaultAccount -inpageProvider.publicConfigStore.subscribe(function (state) { - web3.eth.defaultAccount = state.selectedAddress -}) +// setup dapp auto reload AND proxy web3 +setupDappAutoReload(web3, inpageProvider._publicConfigStore) -inpageProvider.publicConfigStore.subscribe(function (state) { - if (state.onboardingcomplete) { - window.postMessage('onboardingcomplete', '*') - } -}) +// +// end deprecate:2020-01-13 +// + +window.ethereum = proxiedInpageProvider diff --git a/app/scripts/lib/account-tracker.js b/app/scripts/lib/account-tracker.js index 3137ea8a3..8fef712fe 100644 --- a/app/scripts/lib/account-tracker.js +++ b/app/scripts/lib/account-tracker.js @@ -124,7 +124,9 @@ class AccountTracker { // save accounts state this.store.updateState({ accounts }) // fetch balances for the accounts if there is block number ready - if (!this._currentBlockNumber) return + if (!this._currentBlockNumber) { + return + } this._updateAccounts() } @@ -158,7 +160,9 @@ class AccountTracker { // block gasLimit polling shouldn't be in account-tracker shouldn't be here... const currentBlock = await this._query.getBlockByNumber(blockNumber, false) - if (!currentBlock) return + if (!currentBlock) { + return + } const currentBlockGasLimit = currentBlock.gasLimit this.store.updateState({ currentBlockGasLimit }) @@ -218,7 +222,9 @@ class AccountTracker { // update accounts state const { accounts } = this.store.getState() // only populate if the entry is still present - if (!accounts[address]) return + if (!accounts[address]) { + return + } accounts[address] = result this.store.updateState({ accounts }) } diff --git a/app/scripts/lib/auto-reload.js b/app/scripts/lib/auto-reload.js index fd209c230..e2b9c17bd 100644 --- a/app/scripts/lib/auto-reload.js +++ b/app/scripts/lib/auto-reload.js @@ -1,3 +1,6 @@ + +// TODO:deprecate:2020-01-13 + module.exports = setupDappAutoReload function setupDappAutoReload (web3, observable) { @@ -13,7 +16,7 @@ function setupDappAutoReload (web3, observable) { lastTimeUsed = Date.now() // show warning once on web3 access if (!hasBeenWarned && key !== 'currentProvider') { - console.warn('MetaMask: web3 will be deprecated in the near future in favor of the ethereumProvider\nhttps://medium.com/metamask/4a899ad6e59e') + console.warn(`MetaMask: On 2020-01-13, MetaMask will no longer inject web3. For more information, see: https://medium.com/metamask/no-longer-injecting-web3-js-4a899ad6e59e`) hasBeenWarned = true } // return value normally @@ -28,10 +31,14 @@ function setupDappAutoReload (web3, observable) { observable.subscribe(function (state) { // if the auto refresh on network change is false do not // do anything - if (!window.ethereum.autoRefreshOnNetworkChange) return + if (!window.ethereum.autoRefreshOnNetworkChange) { + return + } // if reload in progress, no need to check reload logic - if (reloadInProgress) return + if (reloadInProgress) { + return + } const currentNetwork = state.networkVersion @@ -42,10 +49,14 @@ function setupDappAutoReload (web3, observable) { } // skip reload logic if web3 not used - if (!lastTimeUsed) return + if (!lastTimeUsed) { + return + } // if network did not change, exit - if (currentNetwork === lastSeenNetwork) return + if (currentNetwork === lastSeenNetwork) { + return + } // initiate page reload reloadInProgress = true diff --git a/app/scripts/lib/buy-eth-url.js b/app/scripts/lib/buy-eth-url.js index 36dec6f61..1580225b0 100644 --- a/app/scripts/lib/buy-eth-url.js +++ b/app/scripts/lib/buy-eth-url.js @@ -13,7 +13,9 @@ module.exports = getBuyEthUrl */ function getBuyEthUrl ({ network, amount, address, service }) { // default service by network if not specified - if (!service) service = getDefaultServiceForNetwork(network) + if (!service) { + service = getDefaultServiceForNetwork(network) + } switch (service) { case 'wyre': @@ -30,8 +32,9 @@ function getBuyEthUrl ({ network, amount, address, service }) { return 'https://github.com/kovan-testnet/faucet' case 'goerli-faucet': return 'https://goerli-faucet.slock.it/' + default: + throw new Error(`Unknown cryptocurrency exchange or faucet: "${service}"`) } - throw new Error(`Unknown cryptocurrency exchange or faucet: "${service}"`) } function getDefaultServiceForNetwork (network) { @@ -46,6 +49,7 @@ function getDefaultServiceForNetwork (network) { return 'kovan-faucet' case '5': return 'goerli-faucet' + default: + throw new Error(`No default cryptocurrency exchange or faucet for networkId: "${network}"`) } - throw new Error(`No default cryptocurrency exchange or faucet for networkId: "${network}"`) } diff --git a/app/scripts/lib/cleanErrorStack.js b/app/scripts/lib/cleanErrorStack.js index 8adf55db7..58f7ccfdf 100644 --- a/app/scripts/lib/cleanErrorStack.js +++ b/app/scripts/lib/cleanErrorStack.js @@ -4,10 +4,10 @@ * @returns {Error} Error with clean stack trace. */ function cleanErrorStack (err) { - var name = err.name + let name = err.name name = (name === undefined) ? 'Error' : String(name) - var msg = err.message + let msg = err.message msg = (msg === undefined) ? '' : String(msg) if (name === '') { diff --git a/app/scripts/lib/createDnodeRemoteGetter.js b/app/scripts/lib/createDnodeRemoteGetter.js index b70d218f3..60a775383 100644 --- a/app/scripts/lib/createDnodeRemoteGetter.js +++ b/app/scripts/lib/createDnodeRemoteGetter.js @@ -8,7 +8,9 @@ function createDnodeRemoteGetter (dnode) { }) async function getRemote () { - if (remote) return remote + if (remote) { + return remote + } return await new Promise(resolve => dnode.once('remote', resolve)) } diff --git a/app/scripts/lib/createLoggerMiddleware.js b/app/scripts/lib/createLoggerMiddleware.js index 996c3477c..d95cdb465 100644 --- a/app/scripts/lib/createLoggerMiddleware.js +++ b/app/scripts/lib/createLoggerMiddleware.js @@ -13,7 +13,9 @@ function createLoggerMiddleware (opts) { if (res.error) { log.error('Error in RPC response:\n', res) } - if (req.isMetamaskInternal) return + if (req.isMetamaskInternal) { + return + } log.info(`RPC (${opts.origin}):`, req, '->', res) cb() }) diff --git a/app/scripts/lib/createOriginMiddleware.js b/app/scripts/lib/createOriginMiddleware.js index 98bb0e3b3..4ff9a3386 100644 --- a/app/scripts/lib/createOriginMiddleware.js +++ b/app/scripts/lib/createOriginMiddleware.js @@ -1,3 +1,4 @@ + module.exports = createOriginMiddleware /** diff --git a/app/scripts/lib/ens-ipfs/setup.js b/app/scripts/lib/ens-ipfs/setup.js index e4eddd494..f4f07102f 100644 --- a/app/scripts/lib/ens-ipfs/setup.js +++ b/app/scripts/lib/ens-ipfs/setup.js @@ -33,7 +33,9 @@ function setupEnsIpfsResolver ({ provider, getCurrentNetwork }) { const domainParts = name.split('.') const topLevelDomain = domainParts[domainParts.length - 1] // if unsupported TLD, abort - if (!supportedTopLevelDomains.includes(topLevelDomain)) return + if (!supportedTopLevelDomains.includes(topLevelDomain)) { + return + } // otherwise attempt resolve attemptResolve({ tabId, name, path, search, fragment }) } @@ -48,7 +50,9 @@ function setupEnsIpfsResolver ({ provider, getCurrentNetwork }) { try { // check if ipfs gateway has result const response = await fetch(resolvedUrl, { method: 'HEAD' }) - if (response.status === 200) url = resolvedUrl + if (response.status === 200) { + url = resolvedUrl + } } catch (err) { console.warn(err) } diff --git a/app/scripts/lib/local-store.js b/app/scripts/lib/local-store.js index 8fde2e911..b7212c980 100644 --- a/app/scripts/lib/local-store.js +++ b/app/scripts/lib/local-store.js @@ -1,5 +1,6 @@ const extension = require('extensionizer') const log = require('loglevel') +const { checkForError } = require('./util') /** * A wrapper around the extension's storage local API @@ -20,7 +21,9 @@ module.exports = class ExtensionStore { * @return {Promise<*>} */ async get () { - if (!this.isSupported) return undefined + if (!this.isSupported) { + return undefined + } const result = await this._get() // extension.storage.local always returns an obj // if the object is empty, treat it as undefined @@ -88,17 +91,3 @@ module.exports = class ExtensionStore { function isEmpty (obj) { return Object.keys(obj).length === 0 } - -/** - * Returns an Error if extension.runtime.lastError is present - * this is a workaround for the non-standard error object thats used - * @returns {Error} - */ -function checkForError () { - const lastError = extension.runtime.lastError - if (!lastError) return - // if it quacks like an Error, its an Error - if (lastError.stack && lastError.message) return lastError - // repair incomplete error object (eg chromium v77) - return new Error(lastError.message) -} diff --git a/app/scripts/lib/message-manager.js b/app/scripts/lib/message-manager.js index 8e1ff34b7..a947188e5 100644 --- a/app/scripts/lib/message-manager.js +++ b/app/scripts/lib/message-manager.js @@ -1,7 +1,7 @@ const EventEmitter = require('events') const ObservableStore = require('obs-store') const ethUtil = require('ethereumjs-util') -const { errors: rpcErrors } = require('eth-json-rpc-errors') +const { ethErrors } = require('eth-json-rpc-errors') const createId = require('./random-id') /** @@ -62,7 +62,9 @@ module.exports = class MessageManager extends EventEmitter { */ getUnapprovedMsgs () { return this.messages.filter(msg => msg.status === 'unapproved') - .reduce((result, msg) => { result[msg.id] = msg; return result }, {}) + .reduce((result, msg) => { + result[msg.id] = msg; return result + }, {}) } /** @@ -83,7 +85,7 @@ module.exports = class MessageManager extends EventEmitter { case 'signed': return resolve(data.rawSig) case 'rejected': - return reject(rpcErrors.eth.userRejectedRequest('MetaMask Message Signature: User denied message signature.')) + return reject(ethErrors.provider.userRejectedRequest('MetaMask Message Signature: User denied message signature.')) default: return reject(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)) } @@ -102,12 +104,14 @@ module.exports = class MessageManager extends EventEmitter { */ addUnapprovedMessage (msgParams, req) { // add origin from request - if (req) msgParams.origin = req.origin + if (req) { + msgParams.origin = req.origin + } msgParams.data = normalizeMsgData(msgParams.data) // create txData obj with parameters and meta data - var time = (new Date()).getTime() - var msgId = createId() - var msgData = { + const time = (new Date()).getTime() + const msgId = createId() + const msgData = { id: msgId, msgParams: msgParams, time: time, @@ -219,7 +223,9 @@ module.exports = class MessageManager extends EventEmitter { */ _setMsgStatus (msgId, status) { const msg = this.getMsg(msgId) - if (!msg) throw new Error('MessageManager - Message not found for id: "${msgId}".') + if (!msg) { + throw new Error('MessageManager - Message not found for id: "${msgId}".') + } msg.status = status this._updateMsg(msg) this.emit(`${msgId}:${status}`, msg) diff --git a/app/scripts/lib/migrator/index.js b/app/scripts/lib/migrator/index.js index 345ca8001..c1c225fb3 100644 --- a/app/scripts/lib/migrator/index.js +++ b/app/scripts/lib/migrator/index.js @@ -40,8 +40,12 @@ class Migrator extends EventEmitter { try { // attempt migration and validate const migratedData = await migration.migrate(versionedData) - if (!migratedData.data) throw new Error('Migrator - migration returned empty data') - if (migratedData.version !== undefined && migratedData.meta.version !== migration.version) throw new Error('Migrator - Migration did not update version number correctly') + if (!migratedData.data) { + throw new Error('Migrator - migration returned empty data') + } + if (migratedData.version !== undefined && migratedData.meta.version !== migration.version) { + throw new Error('Migrator - Migration did not update version number correctly') + } // accept the migration as good versionedData = migratedData } catch (err) { diff --git a/app/scripts/lib/nodeify.js b/app/scripts/lib/nodeify.js index a813ae679..99b96b356 100644 --- a/app/scripts/lib/nodeify.js +++ b/app/scripts/lib/nodeify.js @@ -1,5 +1,9 @@ const promiseToCallback = require('promise-to-callback') -const callbackNoop = function (err) { if (err) throw err } +const callbackNoop = function (err) { + if (err) { + throw err + } +} /** * A generator that returns a function which, when passed a promise, can treat that promise as a node style callback. diff --git a/app/scripts/lib/notification-manager.js b/app/scripts/lib/notification-manager.js index 721d109a1..85177cceb 100644 --- a/app/scripts/lib/notification-manager.js +++ b/app/scripts/lib/notification-manager.js @@ -18,7 +18,9 @@ class NotificationManager { */ showPopup () { this._getPopup((err, popup) => { - if (err) throw err + if (err) { + throw err + } // Bring focus to chrome popup if (popup) { @@ -28,7 +30,9 @@ class NotificationManager { const {screenX, screenY, outerWidth, outerHeight} = window const notificationTop = Math.round(screenY + (outerHeight / 2) - (NOTIFICATION_HEIGHT / 2)) const notificationLeft = Math.round(screenX + (outerWidth / 2) - (NOTIFICATION_WIDTH / 2)) - const cb = (currentPopup) => { this._popupId = currentPopup.id } + const cb = (currentPopup) => { + this._popupId = currentPopup.id + } // create new notification popup const creation = extension.windows.create({ url: 'notification.html', @@ -50,8 +54,12 @@ class NotificationManager { closePopup () { // closes notification popup this._getPopup((err, popup) => { - if (err) throw err - if (!popup) return + if (err) { + throw err + } + if (!popup) { + return + } extension.windows.remove(popup.id, console.error) }) } @@ -66,7 +74,9 @@ class NotificationManager { */ _getPopup (cb) { this._getWindows((err, windows) => { - if (err) throw err + if (err) { + throw err + } cb(null, this._getPopupIn(windows)) }) } diff --git a/app/scripts/lib/pending-balance-calculator.js b/app/scripts/lib/pending-balance-calculator.js index 0f1dc19a9..dd7fa6de4 100644 --- a/app/scripts/lib/pending-balance-calculator.js +++ b/app/scripts/lib/pending-balance-calculator.js @@ -32,7 +32,9 @@ class PendingBalanceCalculator { ]) const [ balance, pending ] = results - if (!balance) return undefined + if (!balance) { + return undefined + } const pendingValue = pending.reduce((total, tx) => { return total.add(this.calculateMaxCost(tx)) diff --git a/app/scripts/lib/personal-message-manager.js b/app/scripts/lib/personal-message-manager.js index 2a2ab481a..fb30cc03c 100644 --- a/app/scripts/lib/personal-message-manager.js +++ b/app/scripts/lib/personal-message-manager.js @@ -1,7 +1,7 @@ const EventEmitter = require('events') const ObservableStore = require('obs-store') const ethUtil = require('ethereumjs-util') -const { errors: rpcErrors } = require('eth-json-rpc-errors') +const { ethErrors } = require('eth-json-rpc-errors') const createId = require('./random-id') const hexRe = /^[0-9A-Fa-f]+$/g const log = require('loglevel') @@ -65,7 +65,9 @@ module.exports = class PersonalMessageManager extends EventEmitter { */ getUnapprovedMsgs () { return this.messages.filter(msg => msg.status === 'unapproved') - .reduce((result, msg) => { result[msg.id] = msg; return result }, {}) + .reduce((result, msg) => { + result[msg.id] = msg; return result + }, {}) } /** @@ -89,7 +91,7 @@ module.exports = class PersonalMessageManager extends EventEmitter { case 'signed': return resolve(data.rawSig) case 'rejected': - return reject(rpcErrors.eth.userRejectedRequest('MetaMask Message Signature: User denied message signature.')) + return reject(ethErrors.provider.userRejectedRequest('MetaMask Message Signature: User denied message signature.')) default: return reject(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)) } @@ -110,12 +112,14 @@ module.exports = class PersonalMessageManager extends EventEmitter { addUnapprovedMessage (msgParams, req) { log.debug(`PersonalMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`) // add origin from request - if (req) msgParams.origin = req.origin + if (req) { + msgParams.origin = req.origin + } msgParams.data = this.normalizeMsgData(msgParams.data) // create txData obj with parameters and meta data - var time = (new Date()).getTime() - var msgId = createId() - var msgData = { + const time = (new Date()).getTime() + const msgId = createId() + const msgData = { id: msgId, msgParams: msgParams, time: time, @@ -229,7 +233,9 @@ module.exports = class PersonalMessageManager extends EventEmitter { */ _setMsgStatus (msgId, status) { const msg = this.getMsg(msgId) - if (!msg) throw new Error(`PersonalMessageManager - Message not found for id: "${msgId}".`) + if (!msg) { + throw new Error(`PersonalMessageManager - Message not found for id: "${msgId}".`) + } msg.status = status this._updateMsg(msg) this.emit(`${msgId}:${status}`, msg) diff --git a/app/scripts/lib/setupFetchDebugging.js b/app/scripts/lib/setupFetchDebugging.js index 431340e2b..d5c01b1f2 100644 --- a/app/scripts/lib/setupFetchDebugging.js +++ b/app/scripts/lib/setupFetchDebugging.js @@ -7,7 +7,9 @@ module.exports = setupFetchDebugging // function setupFetchDebugging () { - if (!global.fetch) return + if (!global.fetch) { + return + } const originalFetch = global.fetch global.fetch = wrappedFetch diff --git a/app/scripts/lib/setupMetamaskMeshMetrics.js b/app/scripts/lib/setupMetamaskMeshMetrics.js index 6f0d86b28..b520ceaa7 100644 --- a/app/scripts/lib/setupMetamaskMeshMetrics.js +++ b/app/scripts/lib/setupMetamaskMeshMetrics.js @@ -22,7 +22,9 @@ function setupMetamaskMeshMetrics () { function submitMeshMetricsEntry (message) { // ignore if we haven't loaded yet - if (!didLoad) return + if (!didLoad) { + return + } // submit the message testingContainer.contentWindow.postMessage(message, targetOrigin) } diff --git a/app/scripts/lib/setupSentry.js b/app/scripts/lib/setupSentry.js index ba0e17df0..096bd3454 100644 --- a/app/scripts/lib/setupSentry.js +++ b/app/scripts/lib/setupSentry.js @@ -67,11 +67,15 @@ function simplifyErrorMessages (report) { function rewriteErrorMessages (report, rewriteFn) { // rewrite top level message - if (typeof report.message === 'string') report.message = rewriteFn(report.message) + if (typeof report.message === 'string') { + report.message = rewriteFn(report.message) + } // rewrite each exception message if (report.exception && report.exception.values) { report.exception.values.forEach(item => { - if (typeof item.value === 'string') item.value = rewriteFn(item.value) + if (typeof item.value === 'string') { + item.value = rewriteFn(item.value) + } }) } } @@ -91,7 +95,9 @@ function rewriteReportUrls (report) { function toMetamaskUrl (origUrl) { const filePath = origUrl.split(location.origin)[1] - if (!filePath) return origUrl + if (!filePath) { + return origUrl + } const metamaskUrl = `metamask${filePath}` return metamaskUrl } diff --git a/app/scripts/lib/stream-utils.js b/app/scripts/lib/stream-utils.js index 3dbc064b5..66b3107f4 100644 --- a/app/scripts/lib/stream-utils.js +++ b/app/scripts/lib/stream-utils.js @@ -43,7 +43,9 @@ function setupMultiplex (connectionStream) { mux, connectionStream, (err) => { - if (err) console.error(err) + if (err) { + console.error(err) + } } ) return mux diff --git a/app/scripts/lib/typed-message-manager.js b/app/scripts/lib/typed-message-manager.js index e4d3e842b..5e3c48038 100644 --- a/app/scripts/lib/typed-message-manager.js +++ b/app/scripts/lib/typed-message-manager.js @@ -2,7 +2,7 @@ const EventEmitter = require('events') const ObservableStore = require('obs-store') const createId = require('./random-id') const assert = require('assert') -const { errors: rpcErrors } = require('eth-json-rpc-errors') +const { ethErrors } = require('eth-json-rpc-errors') const sigUtil = require('eth-sig-util') const log = require('loglevel') const jsonschema = require('jsonschema') @@ -58,7 +58,9 @@ module.exports = class TypedMessageManager extends EventEmitter { */ getUnapprovedMsgs () { return this.messages.filter(msg => msg.status === 'unapproved') - .reduce((result, msg) => { result[msg.id] = msg; return result }, {}) + .reduce((result, msg) => { + result[msg.id] = msg; return result + }, {}) } /** @@ -79,7 +81,7 @@ module.exports = class TypedMessageManager extends EventEmitter { case 'signed': return resolve(data.rawSig) case 'rejected': - return reject(rpcErrors.eth.userRejectedRequest('MetaMask Message Signature: User denied message signature.')) + return reject(ethErrors.provider.userRejectedRequest('MetaMask Message Signature: User denied message signature.')) case 'errored': return reject(new Error(`MetaMask Message Signature: ${data.error}`)) default: @@ -103,13 +105,15 @@ module.exports = class TypedMessageManager extends EventEmitter { msgParams.version = version this.validateParams(msgParams) // add origin from request - if (req) msgParams.origin = req.origin + if (req) { + msgParams.origin = req.origin + } log.debug(`TypedMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`) // create txData obj with parameters and meta data - var time = (new Date()).getTime() - var msgId = createId() - var msgData = { + const time = (new Date()).getTime() + const msgId = createId() + const msgData = { id: msgId, msgParams: msgParams, time: time, @@ -149,7 +153,9 @@ module.exports = class TypedMessageManager extends EventEmitter { assert.ok('from' in params, 'Params must include a from field.') assert.equal(typeof params.from, 'string', 'From field must be a string.') assert.equal(typeof params.data, 'string', 'Data must be passed as a valid JSON string.') - assert.doesNotThrow(() => { data = JSON.parse(params.data) }, 'Data must be passed as a valid JSON string.') + assert.doesNotThrow(() => { + data = JSON.parse(params.data) + }, 'Data must be passed as a valid JSON string.') const validation = jsonschema.validate(data, sigUtil.TYPED_MESSAGE_SCHEMA) assert.ok(data.primaryType in data.types, `Primary type of "${data.primaryType}" has no type definition.`) assert.equal(validation.errors.length, 0, 'Data must conform to EIP-712 schema. See https://git.io/fNtcx.') @@ -157,6 +163,8 @@ module.exports = class TypedMessageManager extends EventEmitter { const activeChainId = parseInt(this.networkController.getNetworkState()) chainId && assert.equal(chainId, activeChainId, `Provided chainId (${chainId}) must match the active chainId (${activeChainId})`) break + default: + assert.fail(`Unknown params.version ${params.version}`) } } @@ -278,7 +286,9 @@ module.exports = class TypedMessageManager extends EventEmitter { */ _setMsgStatus (msgId, status) { const msg = this.getMsg(msgId) - if (!msg) throw new Error('TypedMessageManager - Message not found for id: "${msgId}".') + if (!msg) { + throw new Error('TypedMessageManager - Message not found for id: "${msgId}".') + } msg.status = status this._updateMsg(msg) this.emit(`${msgId}:${status}`, msg) diff --git a/app/scripts/lib/util.js b/app/scripts/lib/util.js index 114203d7f..36b836eb1 100644 --- a/app/scripts/lib/util.js +++ b/app/scripts/lib/util.js @@ -1,3 +1,4 @@ +const extension = require('extensionizer') const ethUtil = require('ethereumjs-util') const assert = require('assert') const BN = require('bn.js') @@ -148,6 +149,32 @@ function getRandomArrayItem (array) { return array[Math.floor((Math.random() * array.length))] } +function mapObjectValues (object, cb) { + const mappedObject = {} + Object.keys(object).forEach(key => { + mappedObject[key] = cb(key, object[key]) + }) + return mappedObject +} + +/** + * Returns an Error if extension.runtime.lastError is present + * this is a workaround for the non-standard error object thats used + * @returns {Error} + */ +function checkForError () { + const lastError = extension.runtime.lastError + if (!lastError) { + return + } + // if it quacks like an Error, its an Error + if (lastError.stack && lastError.message) { + return lastError + } + // repair incomplete error object (eg chromium v77) + return new Error(lastError.message) +} + module.exports = { removeListeners, applyListeners, @@ -159,4 +186,6 @@ module.exports = { bnToHex, BnMultiplyByFraction, getRandomArrayItem, + mapObjectValues, + checkForError, } diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 0d7b0a598..a7bf4e261 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -4,13 +4,13 @@ * @license MIT */ +const assert = require('assert').strict const EventEmitter = require('events') const pump = require('pump') const Dnode = require('dnode') -const pify = require('pify') +const extension = require('extensionizer') 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,8 +18,8 @@ const debounce = require('debounce') const createEngineStream = require('json-rpc-middleware-stream/engineStream') 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 createOriginMiddleware = require('./lib/createOriginMiddleware') const providerAsMiddleware = require('eth-json-rpc-middleware/providerAsMiddleware') const {setupMultiplex} = require('./lib/stream-utils.js') const KeyringController = require('eth-keyring-controller') @@ -39,8 +39,8 @@ const TypedMessageManager = require('./lib/typed-message-manager') const TransactionController = require('./controllers/transactions') const TokenRatesController = require('./controllers/token-rates') const DetectTokensController = require('./controllers/detect-tokens') -const ProviderApprovalController = require('./controllers/provider-approval') const ABTestController = require('./controllers/ab-test') +const { PermissionsController } = require('./controllers/permissions/') const nodeify = require('./lib/nodeify') const accountImporter = require('./account-import-strategies') const getBuyEthUrl = require('./lib/buy-eth-url') @@ -56,6 +56,7 @@ const TrezorKeyring = require('eth-trezor-keyring') const LedgerBridgeKeyring = require('@metamask/eth-ledger-bridge-keyring') const EthQuery = require('eth-query') const ethUtil = require('ethereumjs-util') +const nanoid = require('nanoid') const contractMap = require('eth-contract-metadata') const { AddressBookController, @@ -88,16 +89,24 @@ module.exports = class MetamaskController extends EventEmitter { // platform-specific api this.platform = opts.platform + this.getRequestAccountTabIds = opts.getRequestAccountTabIds + this.getOpenMetamaskTabsIds = opts.getOpenMetamaskTabsIds + // observable state store this.store = new ComposableObservableStore(initState) + // external connections by origin + // Do not modify directly. Use the associated methods. + this.connections = {} + // lock to ensure only one vault created at once this.createVaultMutex = new Mutex() - // network store + // next, we will initialize the controllers + // controller initializaiton order matters + this.networkController = new NetworkController(initState.NetworkController) - // preferences controller this.preferencesController = new PreferencesController({ initState: initState.PreferencesController, initLangCode: opts.initLangCode, @@ -105,17 +114,14 @@ module.exports = class MetamaskController extends EventEmitter { network: this.networkController, }) - // app-state controller this.appStateController = new AppStateController({ preferencesStore: this.preferencesController.store, onInactiveTimeout: () => this.setLocked(), initState: initState.AppStateController, }) - // currency controller this.currencyRateController = new CurrencyRateController(undefined, initState.CurrencyController) - // infura controller this.infuraController = new InfuraController({ initState: initState.InfuraController, }) @@ -123,7 +129,7 @@ module.exports = class MetamaskController extends EventEmitter { this.phishingController = new PhishingController() - // rpc provider + // now we can initialize the RPC provider, which other controllers require this.initializeProvider() this.provider = this.networkController.getProviderAndBlockTracker().provider this.blockTracker = this.networkController.getProviderAndBlockTracker().blockTracker @@ -152,7 +158,7 @@ module.exports = class MetamaskController extends EventEmitter { initState: initState.IncomingTransactionsController, }) - // account tracker watches balances, nonces, and any code at their address. + // account tracker watches balances, nonces, and any code at their address this.accountTracker = new AccountTracker({ provider: this.provider, blockTracker: this.blockTracker, @@ -178,6 +184,7 @@ module.exports = class MetamaskController extends EventEmitter { this.onboardingController = new OnboardingController({ initState: initState.OnboardingController, + preferencesController: this.preferencesController, }) // ensure accountTracker updates balances after network change @@ -185,7 +192,6 @@ module.exports = class MetamaskController extends EventEmitter { this.accountTracker._updateAccounts() }) - // key mgmt const additionalKeyrings = [TrezorKeyring, LedgerBridgeKeyring] this.keyringController = new KeyringController({ keyringTypes: additionalKeyrings, @@ -193,16 +199,25 @@ module.exports = class MetamaskController extends EventEmitter { getNetwork: this.networkController.getNetworkState.bind(this.networkController), encryptor: opts.encryptor || undefined, }) - this.keyringController.memStore.subscribe((s) => this._onKeyringControllerUpdate(s)) - // detect tokens controller + this.permissionsController = new PermissionsController({ + keyringController: this.keyringController, + platform: opts.platform, + notifyDomain: this.notifyConnections.bind(this), + notifyAllDomains: this.notifyAllConnections.bind(this), + }, initState.PermissionsController, initState.PermissionsMetadata) + this.detectTokensController = new DetectTokensController({ preferences: this.preferencesController, network: this.networkController, keyringMemStore: this.keyringController.memStore, }) + this.abTestController = new ABTestController({ + initState: initState.ABTestController, + }) + this.addressBookController = new AddressBookController(undefined, initState.AddressBookController) this.threeBoxController = new ThreeBoxController({ @@ -214,9 +229,9 @@ module.exports = class MetamaskController extends EventEmitter { version, }) - // tx mgmt this.txController = new TransactionController({ initState: initState.TransactionController || initState.TransactionManager, + getPermittedAccounts: this.permissionsController.getAccounts.bind(this.permissionsController), networkStore: this.networkController.networkStore, preferencesStore: this.preferencesController.store, txHistoryLimit: 40, @@ -267,18 +282,6 @@ module.exports = class MetamaskController extends EventEmitter { this.isClientOpenAndUnlocked = memState.isUnlocked && this._isClientOpen }) - this.providerApprovalController = new ProviderApprovalController({ - closePopup: opts.closePopup, - initState: initState.ProviderApprovalController, - keyringController: this.keyringController, - openPopup: opts.openPopup, - preferencesController: this.preferencesController, - }) - - this.abTestController = new ABTestController({ - initState: initState.ABTestController, - }) - this.store.updateStructure({ AppStateController: this.appStateController.store, TransactionController: this.txController.store, @@ -291,10 +294,11 @@ module.exports = class MetamaskController extends EventEmitter { InfuraController: this.infuraController.store, CachedBalancesController: this.cachedBalancesController.store, OnboardingController: this.onboardingController.store, - ProviderApprovalController: this.providerApprovalController.store, IncomingTransactionsController: this.incomingTransactionsController.store, - ThreeBoxController: this.threeBoxController.store, ABTestController: this.abTestController.store, + PermissionsController: this.permissionsController.permissions, + PermissionsMetadata: this.permissionsController.store, + ThreeBoxController: this.threeBoxController.store, }) this.memStore = new ComposableObservableStore(null, { @@ -315,11 +319,9 @@ module.exports = class MetamaskController extends EventEmitter { ShapeshiftController: this.shapeshiftController, InfuraController: this.infuraController.store, OnboardingController: this.onboardingController.store, - // ProviderApprovalController - ProviderApprovalController: this.providerApprovalController.store, - ProviderApprovalControllerMemStore: this.providerApprovalController.memStore, IncomingTransactionsController: this.incomingTransactionsController.store, - // ThreeBoxController + PermissionsController: this.permissionsController.permissions, + PermissionsMetadata: this.permissionsController.store, ThreeBoxController: this.threeBoxController.store, ABTestController: this.abTestController.store, // ENS Controller @@ -340,18 +342,15 @@ module.exports = class MetamaskController extends EventEmitter { version, // account mgmt getAccounts: async ({ origin }) => { - // Expose no accounts if this origin has not been approved, preventing - // account-requring RPC methods from completing successfully - const exposeAccounts = this.providerApprovalController.shouldExposeAccounts(origin) - if (origin !== 'metamask' && !exposeAccounts) { return [] } - const isUnlocked = this.keyringController.memStore.getState().isUnlocked - const selectedAddress = this.preferencesController.getSelectedAddress() - // only show address if account is unlocked - if (isUnlocked && selectedAddress) { - return [selectedAddress] - } else { - return [] + if (origin === 'metamask') { + const selectedAddress = this.preferencesController.getSelectedAddress() + return selectedAddress ? [selectedAddress] : [] + } else if ( + this.keyringController.memStore.getState().isUnlocked + ) { + return await this.permissionsController.getAccounts(origin) } + return [] // changing this is a breaking change }, // tx signing processTransaction: this.newUnapprovedTransaction.bind(this), @@ -372,7 +371,7 @@ 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. */ - createPublicConfigStore ({ checkIsEnabled }) { + createPublicConfigStore () { // subset of state for metamask inpage provider const publicConfigStore = new ObservableStore() @@ -385,24 +384,16 @@ module.exports = class MetamaskController extends EventEmitter { } function updatePublicConfigStore (memState) { - const publicState = selectPublicState(memState) - publicConfigStore.putState(publicState) + publicConfigStore.putState(selectPublicState(memState)) } - function selectPublicState ({ isUnlocked, selectedAddress, network, completedOnboarding, provider }) { - const isEnabled = checkIsEnabled() - const isReady = isUnlocked && isEnabled - const result = { + function selectPublicState ({ isUnlocked, network, provider }) { + return { isUnlocked, - isEnabled, - selectedAddress: isReady ? selectedAddress : null, networkVersion: network, - onboardingcomplete: completedOnboarding, chainId: selectChainId({ network, provider }), } - return result } - return publicConfigStore } @@ -434,13 +425,13 @@ module.exports = class MetamaskController extends EventEmitter { */ getApi () { const keyringController = this.keyringController - const preferencesController = this.preferencesController - const txController = this.txController const networkController = this.networkController - const providerApprovalController = this.providerApprovalController const onboardingController = this.onboardingController + const permissionsController = this.permissionsController + const preferencesController = this.preferencesController const threeBoxController = this.threeBoxController const abTestController = this.abTestController + const txController = this.txController return { // etc @@ -498,7 +489,6 @@ module.exports = class MetamaskController extends EventEmitter { setPreference: nodeify(preferencesController.setPreference, preferencesController), completeOnboarding: nodeify(preferencesController.completeOnboarding, preferencesController), addKnownMethodData: nodeify(preferencesController.addKnownMethodData, preferencesController), - unsetMigratedPrivacyMode: nodeify(preferencesController.unsetMigratedPrivacyMode, preferencesController), // BlacklistController whitelistPhishingDomain: this.whitelistPhishingDomain.bind(this), @@ -545,11 +535,6 @@ module.exports = class MetamaskController extends EventEmitter { signTypedMessage: nodeify(this.signTypedMessage, this), cancelTypedMessage: this.cancelTypedMessage.bind(this), - // provider approval - approveProviderRequestByOrigin: providerApprovalController.approveProviderRequestByOrigin.bind(providerApprovalController), - rejectProviderRequestByOrigin: providerApprovalController.rejectProviderRequestByOrigin.bind(providerApprovalController), - clearApprovedOrigins: providerApprovalController.clearApprovedOrigins.bind(providerApprovalController), - // onboarding controller setSeedPhraseBackedUp: nodeify(onboardingController.setSeedPhraseBackedUp, onboardingController), @@ -563,10 +548,21 @@ module.exports = class MetamaskController extends EventEmitter { // a/b test controller getAssignedABTestGroupName: nodeify(abTestController.getAssignedABTestGroupName, abTestController), + + // permissions + approvePermissionsRequest: nodeify(permissionsController.approvePermissionsRequest, permissionsController), + clearPermissions: permissionsController.clearPermissions.bind(permissionsController), + getApprovedAccounts: nodeify(permissionsController.getAccounts.bind(permissionsController)), + rejectPermissionsRequest: nodeify(permissionsController.rejectPermissionsRequest, permissionsController), + removePermissionsFor: permissionsController.removePermissionsFor.bind(permissionsController), + updateExposedAccounts: nodeify(permissionsController.updateExposedAccounts, permissionsController), + legacyExposeAccounts: nodeify(permissionsController.legacyExposeAccounts, permissionsController), + + getRequestAccountTabIds: (cb) => cb(null, this.getRequestAccountTabIds()), + getOpenMetamaskTabsIds: (cb) => cb(null, this.getOpenMetamaskTabsIds()), } } - //============================================================================= // VAULT / KEYRING RELATED METHODS //============================================================================= @@ -1350,10 +1346,10 @@ module.exports = class MetamaskController extends EventEmitter { // setup multiplexing const mux = setupMultiplex(connectionStream) - // connect features - const publicApi = this.setupPublicApi(mux.createStream('publicApi')) - this.setupProviderConnection(mux.createStream('provider'), senderUrl, extensionId, publicApi) - this.setupPublicConfig(mux.createStream('publicConfig'), senderUrl) + + // messages between inpage and background + this.setupProviderConnection(mux.createStream('provider'), senderUrl, extensionId) + this.setupPublicConfig(mux.createStream('publicConfig')) } /** @@ -1409,7 +1405,9 @@ module.exports = class MetamaskController extends EventEmitter { this.activeControllerConnections-- this.emit('controllerConnectionChanged', this.activeControllerConnections) // report any error - if (err) log.error(err) + if (err) { + log.error(err) + } } ) dnode.on('remote', (remote) => { @@ -1429,13 +1427,15 @@ module.exports = class MetamaskController extends EventEmitter { * resource is an extension. * @param {object} publicApi - The public API */ - setupProviderConnection (outStream, senderUrl, extensionId, publicApi) { - const getSiteMetadata = publicApi && publicApi.getSiteMetadata - const engine = this.setupProviderEngine(senderUrl, extensionId, getSiteMetadata) + setupProviderConnection (outStream, senderUrl, extensionId) { + const origin = senderUrl.hostname + const engine = this.setupProviderEngine(senderUrl, extensionId) // setup connection const providerStream = createEngineStream({ engine }) + const connectionId = this.addConnection(origin, { engine }) + pump( outStream, providerStream, @@ -1447,7 +1447,10 @@ module.exports = class MetamaskController extends EventEmitter { mid.destroy() } }) - if (err) log.error(err) + connectionId && this.removeConnection(origin, connectionId) + if (err) { + log.error(err) + } } ) } @@ -1455,7 +1458,8 @@ module.exports = class MetamaskController extends EventEmitter { /** * A method for creating a provider that is safely restricted for the requesting domain. **/ - setupProviderEngine (senderUrl, extensionId, getSiteMetadata) { + setupProviderEngine (senderUrl, extensionId) { + const origin = senderUrl.hostname // setup json rpc engine stack const engine = new RpcEngine() @@ -1469,20 +1473,17 @@ module.exports = class MetamaskController extends EventEmitter { const subscriptionManager = createSubscriptionManager({ provider, blockTracker }) subscriptionManager.events.on('notification', (message) => engine.emit('notification', message)) - // metadata + // append origin to each request engine.push(createOriginMiddleware({ origin })) + // logging engine.push(createLoggerMiddleware({ origin })) // filter and subscription polyfills engine.push(filterMiddleware) engine.push(subscriptionManager.middleware) + // permissions + engine.push(this.permissionsController.createMiddleware({ origin, extensionId })) // watch asset engine.push(this.preferencesController.requestWatchAsset.bind(this.preferencesController)) - // requestAccounts - engine.push(this.providerApprovalController.createMiddleware({ - senderUrl, - extensionId, - getSiteMetadata, - })) // forward to metamask primary provider engine.push(providerAsMiddleware(provider)) return engine @@ -1497,13 +1498,9 @@ module.exports = class MetamaskController extends EventEmitter { * this is a good candidate for deprecation. * * @param {*} outStream - The stream to provide public config over. - * @param {URL} senderUrl - The URL of requesting resource */ - setupPublicConfig (outStream, senderUrl) { - const configStore = this.createPublicConfigStore({ - // check the providerApprovalController's approvedOrigins - checkIsEnabled: () => this.providerApprovalController.shouldExposeAccounts(senderUrl.hostname), - }) + setupPublicConfig (outStream) { + const configStore = this.createPublicConfigStore() const configStream = asStream(configStore) pump( @@ -1512,43 +1509,145 @@ module.exports = class MetamaskController extends EventEmitter { (err) => { configStore.destroy() configStream.destroy() - if (err) log.error(err) + if (err) { + log.error(err) + } } ) } + // manage external connections + + onMessage (message, sender, sendResponse) { + if (!message || !message.type) { + log.debug(`Ignoring invalid message: '${JSON.stringify(message)}'`) + return + } + + let handleMessage + + try { + if (message.type === 'metamask:registerOnboarding') { + assert(sender.tab, 'Missing tab from sender') + assert(sender.tab.id && sender.tab.id !== extension.tabs.TAB_ID_NONE, 'Missing tab ID from sender') + assert(message.location, 'Missing location from message') + + handleMessage = this.onboardingController.registerOnboarding(message.location, sender.tab.id) + } else { + throw new Error(`Unrecognized message type: '${message.type}'`) + } + } catch (error) { + console.error(error) + sendResponse(error) + return true + } + + if (handleMessage) { + handleMessage + .then(() => { + sendResponse(null, true) + }) + .catch((error) => { + console.error(error) + sendResponse(error) + }) + return true + } + } + /** - * A method for providing our public api over a stream. - * This includes a method for setting site metadata like title and image + * Adds a reference to a connection by origin. Ignores the 'metamask' origin. + * Caller must ensure that the returned id is stored such that the reference + * can be deleted later. * - * @param {*} outStream - The stream to provide the api over. + * @param {string} origin - The connection's origin string. + * @param {Object} options - Data associated with the connection + * @param {Object} options.engine - The connection's JSON Rpc Engine + * @returns {string} - The connection's id (so that it can be deleted later) */ - setupPublicApi (outStream) { - const dnode = Dnode() - // connect dnode api to remote connection - pump( - outStream, - dnode, - outStream, - (err) => { - // report any error - if (err) log.error(err) - } - ) + addConnection (origin, { engine }) { - const getRemote = createDnodeRemoteGetter(dnode) - - const publicApi = { - // wrap with an await remote - getSiteMetadata: async () => { - const remote = await getRemote() - return await pify(remote.getSiteMetadata)() - }, + if (origin === 'metamask') { + return null } - return publicApi + if (!this.connections[origin]) { + this.connections[origin] = {} + } + + const id = nanoid() + this.connections[origin][id] = { + engine, + } + + return id } + /** + * Deletes a reference to a connection, by origin and id. + * Ignores unknown origins. + * + * @param {string} origin - The connection's origin string. + * @param {string} id - The connection's id, as returned from addConnection. + */ + removeConnection (origin, id) { + + const connections = this.connections[origin] + if (!connections) { + return + } + + delete connections[id] + + if (Object.keys(connections.length === 0)) { + delete this.connections[origin] + } + } + + /** + * Causes the RPC engines associated with the connections to the given origin + * to emit a notification event with the given payload. + * Does nothing if the extension is locked or the origin is unknown. + * + * @param {string} origin - The connection's origin string. + * @param {any} payload - The event payload. + */ + notifyConnections (origin, payload) { + + const { isUnlocked } = this.getState() + const connections = this.connections[origin] + if (!isUnlocked || !connections) { + return + } + + Object.values(connections).forEach(conn => { + conn.engine && conn.engine.emit('notification', payload) + }) + } + + /** + * Causes the RPC engines associated with all connections to emit a + * notification event with the given payload. + * Does nothing if the extension is locked. + * + * @param {any} payload - The event payload. + */ + notifyAllConnections (payload) { + + const { isUnlocked } = this.getState() + if (!isUnlocked) { + return + } + + Object.values(this.connections).forEach(origin => { + Object.values(origin).forEach(conn => { + conn.engine && conn.engine.emit('notification', payload) + }) + }) + } + + // handlers + /** * Handle a KeyringController update * @param {object} state the KC state @@ -1577,6 +1676,8 @@ module.exports = class MetamaskController extends EventEmitter { } } + // misc + /** * A method for emitting the full MetaMask state to all registered listeners. * @private @@ -1585,6 +1686,10 @@ module.exports = class MetamaskController extends EventEmitter { this.emit('update', this.getState()) } + //============================================================================= + // MISCELLANEOUS + //============================================================================= + /** * A method for estimating a good gas price at recent prices. * Returns the lowest price that would have been included in @@ -1681,10 +1786,14 @@ module.exports = class MetamaskController extends EventEmitter { * @param {string} amount - The amount of ether desired, as a base 10 string. */ buyEth (address, amount) { - if (!amount) amount = '5' + if (!amount) { + amount = '5' + } const network = this.networkController.getNetworkState() const url = getBuyEthUrl({ network, address, amount }) - if (url) this.platform.openWindow({ url }) + if (url) { + this.platform.openWindow({ url }) + } } /** diff --git a/app/scripts/migrations/004.js b/app/scripts/migrations/004.js index cd558300c..a1ae823cd 100644 --- a/app/scripts/migrations/004.js +++ b/app/scripts/migrations/004.js @@ -9,7 +9,9 @@ module.exports = { const safeVersionedData = clone(versionedData) safeVersionedData.meta.version = version try { - if (safeVersionedData.data.config.provider.type !== 'rpc') return Promise.resolve(safeVersionedData) + if (safeVersionedData.data.config.provider.type !== 'rpc') { + return Promise.resolve(safeVersionedData) + } switch (safeVersionedData.data.config.provider.rpcTarget) { case 'https://testrpc.metamask.io/': safeVersionedData.data.config.provider = { @@ -21,6 +23,7 @@ module.exports = { type: 'mainnet', } break + // No default } } catch (_) {} return Promise.resolve(safeVersionedData) diff --git a/app/scripts/migrations/015.js b/app/scripts/migrations/015.js index 5e2f9e63b..3d20b58db 100644 --- a/app/scripts/migrations/015.js +++ b/app/scripts/migrations/015.js @@ -32,8 +32,11 @@ function transformState (state) { if (TransactionController && TransactionController.transactions) { const transactions = TransactionController.transactions newState.TransactionController.transactions = transactions.map((txMeta) => { - if (!txMeta.err) return txMeta - else if (txMeta.err.message === 'Gave up submitting tx.') txMeta.status = 'failed' + if (!txMeta.err) { + return txMeta + } else if (txMeta.err.message === 'Gave up submitting tx.') { + txMeta.status = 'failed' + } return txMeta }) } diff --git a/app/scripts/migrations/016.js b/app/scripts/migrations/016.js index 048c7a40e..76a106ca7 100644 --- a/app/scripts/migrations/016.js +++ b/app/scripts/migrations/016.js @@ -33,7 +33,9 @@ function transformState (state) { const transactions = newState.TransactionController.transactions newState.TransactionController.transactions = transactions.map((txMeta) => { - if (!txMeta.err) return txMeta + if (!txMeta.err) { + return txMeta + } if (txMeta.err === 'transaction with the same hash was already imported.') { txMeta.status = 'submitted' delete txMeta.err diff --git a/app/scripts/migrations/017.js b/app/scripts/migrations/017.js index 5f6d906d6..918df4ade 100644 --- a/app/scripts/migrations/017.js +++ b/app/scripts/migrations/017.js @@ -31,7 +31,9 @@ function transformState (state) { if (TransactionController && TransactionController.transactions) { const transactions = newState.TransactionController.transactions newState.TransactionController.transactions = transactions.map((txMeta) => { - if (!txMeta.status === 'failed') return txMeta + if (!txMeta.status === 'failed') { + return txMeta + } if (txMeta.retryCount > 0 && txMeta.retryCount < 2) { txMeta.status = 'submitted' delete txMeta.err diff --git a/app/scripts/migrations/019.js b/app/scripts/migrations/019.js index 7b726c3e8..f560f5579 100644 --- a/app/scripts/migrations/019.js +++ b/app/scripts/migrations/019.js @@ -35,7 +35,9 @@ function transformState (state) { const transactions = newState.TransactionController.transactions newState.TransactionController.transactions = transactions.map((txMeta, _, txList) => { - if (txMeta.status !== 'submitted') return txMeta + if (txMeta.status !== 'submitted') { + return txMeta + } const confirmedTxs = txList.filter((tx) => tx.status === 'confirmed') .filter((tx) => tx.txParams.from === txMeta.txParams.from) diff --git a/app/scripts/migrations/022.js b/app/scripts/migrations/022.js index 1fbe241e6..eeb142b7a 100644 --- a/app/scripts/migrations/022.js +++ b/app/scripts/migrations/022.js @@ -33,7 +33,9 @@ function transformState (state) { const transactions = newState.TransactionController.transactions newState.TransactionController.transactions = transactions.map((txMeta) => { - if (txMeta.status !== 'submitted' || txMeta.submittedTime) return txMeta + if (txMeta.status !== 'submitted' || txMeta.submittedTime) { + return txMeta + } txMeta.submittedTime = (new Date()).getTime() return txMeta }) diff --git a/app/scripts/migrations/023.js b/app/scripts/migrations/023.js index 18493a789..84532537e 100644 --- a/app/scripts/migrations/023.js +++ b/app/scripts/migrations/023.js @@ -33,7 +33,9 @@ function transformState (state) { if (TransactionController && TransactionController.transactions) { const transactions = newState.TransactionController.transactions - if (transactions.length <= 40) return newState + if (transactions.length <= 40) { + return newState + } const reverseTxList = transactions.reverse() let stripping = true @@ -44,8 +46,11 @@ function transformState (state) { txMeta.status === 'confirmed' || txMeta.status === 'dropped') }) - if (txIndex < 0) stripping = false - else reverseTxList.splice(txIndex, 1) + if (txIndex < 0) { + stripping = false + } else { + reverseTxList.splice(txIndex, 1) + } } newState.TransactionController.transactions = reverseTxList.reverse() diff --git a/app/scripts/migrations/024.js b/app/scripts/migrations/024.js index 5ffaea377..fda463e1a 100644 --- a/app/scripts/migrations/024.js +++ b/app/scripts/migrations/024.js @@ -25,7 +25,9 @@ module.exports = { function transformState (state) { const newState = state - if (!newState.TransactionController) return newState + if (!newState.TransactionController) { + return newState + } const transactions = newState.TransactionController.transactions newState.TransactionController.transactions = transactions.map((txMeta, _) => { if ( diff --git a/app/scripts/migrations/025.js b/app/scripts/migrations/025.js index fd4faa782..48bff962d 100644 --- a/app/scripts/migrations/025.js +++ b/app/scripts/migrations/025.js @@ -29,7 +29,9 @@ function transformState (state) { if (newState.TransactionController.transactions) { const transactions = newState.TransactionController.transactions newState.TransactionController.transactions = transactions.map((txMeta) => { - if (txMeta.status !== 'unapproved') return txMeta + if (txMeta.status !== 'unapproved') { + return txMeta + } txMeta.txParams = normalizeTxParams(txMeta.txParams) return txMeta }) @@ -54,7 +56,9 @@ function normalizeTxParams (txParams) { // apply only keys in the whiteList const normalizedTxParams = {} Object.keys(whiteList).forEach((key) => { - if (txParams[key]) normalizedTxParams[key] = whiteList[key](txParams[key]) + if (txParams[key]) { + normalizedTxParams[key] = whiteList[key](txParams[key]) + } }) return normalizedTxParams diff --git a/app/scripts/migrations/029.js b/app/scripts/migrations/029.js index e17479ccc..59724c154 100644 --- a/app/scripts/migrations/029.js +++ b/app/scripts/migrations/029.js @@ -24,4 +24,3 @@ module.exports = { return isApproved && now - createdTime > unacceptableDelay }), } - diff --git a/app/scripts/migrations/031.js b/app/scripts/migrations/031.js index 9c8cbeb09..927de98c4 100644 --- a/app/scripts/migrations/031.js +++ b/app/scripts/migrations/031.js @@ -3,7 +3,7 @@ const version = 31 const clone = require('clone') /* - * The purpose of this migration is to properly set the completedOnboarding flag baesd on the state + * The purpose of this migration is to properly set the completedOnboarding flag based on the state * of the KeyringController. */ module.exports = { diff --git a/app/scripts/migrations/040.js b/app/scripts/migrations/040.js new file mode 100644 index 000000000..042f499b2 --- /dev/null +++ b/app/scripts/migrations/040.js @@ -0,0 +1,23 @@ +const version = 40 +const clone = require('clone') + +/** + * Site connections are now managed by the PermissionsController, and the + * ProviderApprovalController is removed. This migration deletes all + * ProviderApprovalController state. + */ +module.exports = { + version, + migrate: async function (originalVersionedData) { + const versionedData = clone(originalVersionedData) + versionedData.meta.version = version + const state = versionedData.data + versionedData.data = transformState(state) + return versionedData + }, +} + +function transformState (state) { + delete state.ProviderApprovalController + return state +} diff --git a/app/scripts/platforms/extension.js b/app/scripts/platforms/extension.js index d54a8a7b3..5ae05d230 100644 --- a/app/scripts/platforms/extension.js +++ b/app/scripts/platforms/extension.js @@ -1,7 +1,7 @@ const extension = require('extensionizer') const {createExplorerLink: explorerLink} = require('etherscan-link') -const {getEnvironmentType} = require('../lib/util') +const { getEnvironmentType, checkForError } = require('../lib/util') const {ENVIRONMENT_TYPE_BACKGROUND} = require('../lib/enums') class ExtensionPlatform { @@ -66,6 +66,58 @@ class ExtensionPlatform { } } + queryTabs () { + return new Promise((resolve, reject) => { + extension.tabs.query({}, tabs => { + const err = checkForError() + if (err) { + reject(err) + } else { + resolve(tabs) + } + }) + }) + } + + currentTab () { + return new Promise((resolve, reject) => { + extension.tabs.getCurrent(tab => { + const err = checkForError() + if (err) { + reject(err) + } else { + resolve(tab) + } + }) + }) + } + + switchToTab (tabId) { + return new Promise((resolve, reject) => { + extension.tabs.update(tabId, {highlighted: true}, (tab) => { + const err = checkForError() + if (err) { + reject(err) + } else { + resolve(tab) + } + }) + }) + } + + closeTab (tabId) { + return new Promise((resolve, reject) => { + extension.tabs.remove(tabId, () => { + const err = checkForError() + if (err) { + reject(err) + } else { + resolve() + } + }) + }) + } + _showConfirmedTransaction (txMeta) { this._subscribeToNotificationClicked() diff --git a/development/announcer.js b/development/announcer.js index ea1bfdd36..8b0c1de27 100644 --- a/development/announcer.js +++ b/development/announcer.js @@ -1,11 +1,11 @@ -var manifest = require('../app/manifest.json') -var version = manifest.version +const manifest = require('../app/manifest.json') +const version = manifest.version -var fs = require('fs') -var path = require('path') -var changelog = fs.readFileSync(path.join(__dirname, '..', 'CHANGELOG.md')).toString() +const fs = require('fs') +const path = require('path') +const changelog = fs.readFileSync(path.join(__dirname, '..', 'CHANGELOG.md')).toString() -var log = changelog.split(version)[1].split('##')[0].trim() +const log = changelog.split(version)[1].split('##')[0].trim() const msg = `*MetaMask ${version}* now published! It should auto-update soon!\n${log}` diff --git a/development/auto-changelog.sh b/development/auto-changelog.sh index 3ed059b3d..26ab8e93f 100755 --- a/development/auto-changelog.sh +++ b/development/auto-changelog.sh @@ -10,7 +10,7 @@ git fetch --tags most_recent_tag="$(git describe --tags "$(git rev-list --tags --max-count=1)")" -git rev-list "${most_recent_tag}"..HEAD | while read commit +git rev-list "${most_recent_tag}"..HEAD | while read -r commit do subject="$(git show -s --format="%s" "$commit")" diff --git a/development/mock-3box.js b/development/mock-3box.js index 53f228f21..a26e02a82 100644 --- a/development/mock-3box.js +++ b/development/mock-3box.js @@ -28,7 +28,9 @@ class Mock3Box { static openBox (address) { this.address = address return Promise.resolve({ - onSyncDone: cb => { setTimeout(cb, 200) }, + onSyncDone: cb => { + setTimeout(cb, 200) + }, openSpace: async (spaceName, config) => { const { onSyncDone } = config this.spaceName = spaceName diff --git a/development/mock-dev.js b/development/mock-dev.js index 8da625149..51a8cde46 100644 --- a/development/mock-dev.js +++ b/development/mock-dev.js @@ -10,13 +10,13 @@ * without having to re-open the plugin or even re-build it. */ +import React from 'react' const render = require('react-dom').render -const h = require('react-hyperscript') const Root = require('../ui/app/pages') const configureStore = require('../ui/app/store/store') const actions = require('../ui/app/store/actions') const backGroundConnectionModifiers = require('./backGroundConnectionModifiers') -const Selector = require('./selector') +import Selector from './selector' const MetamaskController = require('../app/scripts/metamask-controller') const firstTimeState = require('../app/scripts/first-time-state') const ExtensionPlatform = require('../app/scripts/platforms/extension') @@ -94,7 +94,7 @@ function modifyBackgroundConnection (backgroundConnectionModifier) { } // parse opts -var store = configureStore(firstState) +const store = configureStore(firstState) // start app startApp() @@ -106,40 +106,38 @@ function startApp () { body.appendChild(container) render( - h('.super-dev-container', [ - - h('button', { - onClick: (ev) => { +
    + + +
    + +
    +
    , + container, + ) } diff --git a/development/rollback.sh b/development/rollback.sh index 639d72a67..a3040e6f1 100755 --- a/development/rollback.sh +++ b/development/rollback.sh @@ -4,20 +4,20 @@ echo "Rolling back to version $1" # Checkout branch to increment version -git checkout -b version-increment-$1 +git checkout -b "version-increment-$1" yarn version:bump patch # Store the new version name -NEW_VERSION=$(cat app/manifest.json | jq -r .version) +NEW_VERSION=$(jq -r .version < app/manifest.json) # Make sure origin tags are loaded git fetch origin # check out the rollback branch -git checkout origin/v$1 +git checkout "origin/v$1" # Create the rollback branch. -git checkout -b Version-$NEW_VERSION-Rollback-to-$1 +git checkout -b "Version-$NEW_VERSION-Rollback-to-$1" # Set the version files to the next one. git checkout master CHANGELOG.md @@ -28,8 +28,8 @@ git commit -m "Version $NEW_VERSION (Rollback to $1)" git push -u origin HEAD # Create tag and push that up too -git tag v${NEW_VERSION} -git push origin v${NEW_VERSION} +git tag "v${NEW_VERSION}" +git push origin "v${NEW_VERSION}" # Cleanup version branch -git branch -D version-increment-$1 +git branch -D "version-increment-$1" diff --git a/development/selector.js b/development/selector.js index 2673d0fe3..01011c710 100644 --- a/development/selector.js +++ b/development/selector.js @@ -1,42 +1,49 @@ -const Component = require('react').Component -const h = require('react-hyperscript') -const inherits = require('util').inherits +import PropTypes from 'prop-types' +import React, {Component} from 'react' -module.exports = NewComponent +export default class Selector extends Component { + state = {} -inherits(NewComponent, Component) -function NewComponent () { - Component.call(this) + render () { + const { + states, + selectedKey, + actions, + store, + modifyBackgroundConnection, + backGroundConnectionModifiers, + } = this.props + const selected = this.state.selected || selectedKey + + return ( + + ) + } } -NewComponent.prototype.render = function () { - const props = this.props - const { - states, - selectedKey, - actions, - store, - modifyBackgroundConnection, - backGroundConnectionModifiers, - } = props - - const state = this.state || {} - const selected = state.selected || selectedKey - - return h('select', { - style: { - margin: '20px 20px 0px', - }, - value: selected, - onChange: (event) => { - const selectedKey = event.target.value - const backgroundConnectionModifier = backGroundConnectionModifiers[selectedKey] - modifyBackgroundConnection(backgroundConnectionModifier || {}) - store.dispatch(actions.update(selectedKey)) - this.setState({ selected: selectedKey }) - }, - }, Object.keys(states).map((stateName) => { - return h('option', { value: stateName }, stateName) - })) - +Selector.propTypes = { + states: PropTypes.object.isRequired, + selectedKey: PropTypes.string.isRequired, + actions: PropTypes.object.isRequired, + store: PropTypes.object.isRequired, + modifyBackgroundConnection: PropTypes.func.isRequired, + backGroundConnectionModifiers: PropTypes.object.isRequired, } diff --git a/development/show-deps-install-scripts.js b/development/show-deps-install-scripts.js index 03a9bb859..419c9d25f 100644 --- a/development/show-deps-install-scripts.js +++ b/development/show-deps-install-scripts.js @@ -5,7 +5,9 @@ const readInstalled = require('read-installed') const installScripts = ['preinstall', 'install', 'postinstall'] readInstalled('./', { dev: true }, function (err, data) { - if (err) throw err + if (err) { + throw err + } const deps = data.dependencies Object.entries(deps).forEach(([packageName, packageData]) => { @@ -13,12 +15,20 @@ readInstalled('./', { dev: true }, function (err, data) { const scriptKeys = Reflect.ownKeys(packageScripts) const hasInstallScript = installScripts.some(installKey => scriptKeys.includes(installKey)) - if (!hasInstallScript) return + if (!hasInstallScript) { + return + } const matchingScripts = {} - if (packageScripts.preinstall) matchingScripts.preinstall = packageScripts.preinstall - if (packageScripts.install) matchingScripts.install = packageScripts.install - if (packageScripts.postinstall) matchingScripts.postinstall = packageScripts.postinstall + if (packageScripts.preinstall) { + matchingScripts.preinstall = packageScripts.preinstall + } + if (packageScripts.install) { + matchingScripts.install = packageScripts.install + } + if (packageScripts.postinstall) { + matchingScripts.postinstall = packageScripts.postinstall + } const scriptNames = Reflect.ownKeys(matchingScripts) const relativePath = path.relative(process.cwd(), packageData.path) diff --git a/development/sourcemap-validator.js b/development/sourcemap-validator.js index 546745f16..22c448862 100644 --- a/development/sourcemap-validator.js +++ b/development/sourcemap-validator.js @@ -52,7 +52,9 @@ async function validateSourcemapForFile ({ buildName }) { const consumer = await new SourceMapConsumer(rawSourceMap) const hasContentsOfAllSources = consumer.hasContentsOfAllSources() - if (!hasContentsOfAllSources) console.warn('SourcemapValidator - missing content of some sources...') + if (!hasContentsOfAllSources) { + console.warn('SourcemapValidator - missing content of some sources...') + } console.log(` sampling from ${consumer.sources.length} files`) let sampleCount = 0 @@ -94,8 +96,10 @@ async function validateSourcemapForFile ({ buildName }) { } function indicesOf (substring, string) { - var a = [] - var i = -1 - while ((i = string.indexOf(substring, i + 1)) >= 0) a.push(i) + const a = [] + let i = -1 + while ((i = string.indexOf(substring, i + 1)) >= 0) { + a.push(i) + } return a } diff --git a/development/static-server.js b/development/static-server.js new file mode 100644 index 000000000..d8f22cabe --- /dev/null +++ b/development/static-server.js @@ -0,0 +1,92 @@ +const fs = require('fs') +const http = require('http') +const path = require('path') + +const chalk = require('chalk') +const pify = require('pify') +const serveHandler = require('serve-handler') + +const fsStat = pify(fs.stat) +const DEFAULT_PORT = 9080 + +const onResponse = (request, response) => { + if (response.statusCode >= 400) { + console.log(chalk`{gray '-->'} {red ${response.statusCode}} ${request.url}`) + } else if (response.statusCode >= 200 && response.statusCode < 300) { + console.log(chalk`{gray '-->'} {green ${response.statusCode}} ${request.url}`) + } else { + console.log(chalk`{gray '-->'} {green.dim ${response.statusCode}} ${request.url}`) + } +} +const onRequest = (request, response) => { + console.log(chalk`{gray '<--'} {blue [${request.method}]} ${request.url}`) + response.on('finish', () => onResponse(request, response)) +} + +const startServer = ({ port, rootDirectory }) => { + const server = http.createServer((request, response) => { + if (request.url.startsWith('/node_modules/')) { + request.url = request.url.substr(14) + return serveHandler(request, response, { + directoryListing: false, + public: path.resolve('./node_modules'), + }) + } + return serveHandler(request, response, { + directoryListing: false, + public: rootDirectory, + }) + }) + + server.on('request', onRequest) + + server.listen(port, () => { + console.log(`Running at http://localhost:${port}`) + }) +} + +const parsePort = (portString) => { + const port = Number(portString) + if (!Number.isInteger(port)) { + throw new Error(`Port '${portString}' is invalid; must be an integer`) + } else if (port < 0 || port > 65535) { + throw new Error(`Port '${portString}' is out of range; must be between 0 and 65535 inclusive`) + } + return port +} + +const parseDirectoryArgument = async (pathString) => { + const resolvedPath = path.resolve(pathString) + const directoryStats = await fsStat(resolvedPath) + if (!directoryStats.isDirectory()) { + throw new Error(`Invalid path '${pathString}'; must be a directory`) + } + return resolvedPath +} + +const main = async () => { + const args = process.argv.slice(2) + + const options = { + port: process.env.port || DEFAULT_PORT, + rootDirectory: path.resolve('.'), + } + + while (args.length) { + if (/^(--port|-p)$/i.test(args[0])) { + if (args[1] === undefined) { + throw new Error('Missing port argument') + } + options.port = parsePort(args[1]) + args.splice(0, 2) + } else { + options.rootDirectory = await parseDirectoryArgument(args[0]) + args.splice(0, 1) + } + } + + startServer(options) +} + +main() + .catch(console.error) diff --git a/development/verify-locale-strings.js b/development/verify-locale-strings.js index aa0c4503f..c6875b36c 100644 --- a/development/verify-locale-strings.js +++ b/development/verify-locale-strings.js @@ -161,11 +161,11 @@ async function verifyEnglishLocale (fix = false) { const englishLocale = await getLocale('en') const javascriptFiles = await findJavascriptFiles(path.resolve(__dirname, '..', 'ui')) - const regex = /'(\w+)'/g + const regex = /'(\w+)'|"(\w+)"/g const usedMessages = new Set() for await (const fileContents of getFileContents(javascriptFiles)) { for (const match of matchAll.call(fileContents, regex)) { - usedMessages.add(match[1]) + usedMessages.add(match[1] || match[2]) } } diff --git a/development/version-bump.js b/development/version-bump.js index 66b6baffe..6ae21b782 100644 --- a/development/version-bump.js +++ b/development/version-bump.js @@ -40,6 +40,8 @@ function newVersionFrom (manifest, bumpType) { case 'patch': segments[2] += 1 break + default: + throw new Error(`invalid bumpType ${bumpType}`) } return segments.map(String).join('.') diff --git a/docs/porting_to_new_environment.md b/docs/porting_to_new_environment.md index 9cb8e9c2d..92f06f37d 100644 --- a/docs/porting_to_new_environment.md +++ b/docs/porting_to_new_environment.md @@ -18,8 +18,8 @@ const providerFromEngine = require('eth-json-rpc-middleware/providerFromEngine') /** * returns a provider restricted to the requesting domain **/ -function incomingConnection (domain, getSiteMetadata) { - const engine = metamaskController.setupProviderEngine(domain, getSiteMetadata) +function incomingConnection (domain) { + const engine = metamaskController.setupProviderEngine(domain) const provider = providerFromEngine(engine) return provider } @@ -32,15 +32,6 @@ const filterMiddleware = engine._middleware.filter(mid => mid.name === 'filterMi filterMiddleware.destroy() ``` -### getSiteMetadata() - -This method is used to enhance our confirmation screens with images and text representing the requesting domain. - -It should return a promise that resolves with an object with the following properties: - -- `name`: The requesting site's name. -- `icon`: A URI representing the site's logo. - ### Using the Streams Interface Only use this if you intend to construct the [metamask-inpage-provider](https://github.com/MetaMask/metamask-inpage-provider) over a stream! diff --git a/gulpfile.js b/gulpfile.js index 3e4a3b7ff..d5d74333a 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -428,7 +428,9 @@ function createTasksForBuildJs ({ rootDir, taskPrefix, bundleTaskOpts, destinati // compose into larger task const subtasks = [] subtasks.push(gulp.parallel(buildPhase1.map(file => `${taskPrefix}:${file}`))) - if (buildPhase2.length) subtasks.push(gulp.parallel(buildPhase2.map(file => `${taskPrefix}:${file}`))) + if (buildPhase2.length) { + subtasks.push(gulp.parallel(buildPhase2.map(file => `${taskPrefix}:${file}`))) + } gulp.task(taskPrefix, gulp.series(subtasks)) } diff --git a/package.json b/package.json index 8fb06842d..46dfaf475 100644 --- a/package.json +++ b/package.json @@ -10,10 +10,12 @@ "start:test": "gulp dev:test", "build:test": "gulp build:test", "test": "yarn test:unit && yarn lint", - "dapp": "static-server test/e2e/contract-test --port 8080", - "dapp-chain": "GANACHE_ARGS='-b 2' concurrently -k -n ganache,dapp -p '[{time}][{name}]' 'yarn ganache:start' 'sleep 5 && static-server test/e2e/contract-test --port 8080'", + "dapp": "node development/static-server.js test/e2e/contract-test --port 8080", + "dapp-chain": "GANACHE_ARGS='-b 2' concurrently -k -n ganache,dapp -p '[{time}][{name}]' 'yarn ganache:start' 'sleep 5 && node development/static-server.js test/e2e/contract-test --port 8080'", + "forwarder": "node ./development/static-server.js ./node_modules/@metamask/forwarder/dist/ --port 9010", + "dapp-forwarder": "concurrently -k -n forwarder,dapp -p '[{time}][{name}]' 'yarn forwarder' 'yarn dapp'", "watch:test:unit": "nodemon --exec \"yarn test:unit\" ./test ./app ./ui", - "sendwithprivatedapp": "static-server test/e2e/send-eth-with-private-key-test --port 8080", + "sendwithprivatedapp": "node development/static-server.js test/e2e/send-eth-with-private-key-test --port 8080", "test:unit": "cross-env METAMASK_ENV=test mocha --exit --require test/setup.js --recursive \"test/unit/**/*.js\" \"ui/app/**/*.test.js\"", "test:unit:global": "mocha test/unit-global/*", "test:single": "cross-env METAMASK_ENV=test mocha --require test/helper.js", @@ -37,7 +39,9 @@ "lint:fix": "eslint . --ext js,json --fix", "lint:changed": "{ git ls-files --others --exclude-standard ; git diff-index --name-only --diff-filter=d HEAD ; } | grep --regexp='[.]js$' --regexp='[.]json$' | tr '\\n' '\\0' | xargs -0 eslint", "lint:changed:fix": "{ git ls-files --others --exclude-standard ; git diff-index --name-only --diff-filter=d HEAD ; } | grep --regexp='[.]js$' --regexp='[.]json$' | tr '\\n' '\\0' | xargs -0 eslint --fix", + "lint:shellcheck": "shellcheck --version && find . -type f -name '*.sh' ! -path './node_modules/*' -print0 | xargs -0 shellcheck", "verify-locales": "node ./development/verify-locale-strings.js", + "verify-locales:fix": "node ./development/verify-locale-strings.js --fix", "mozilla-lint": "addons-linter dist/firefox", "watch": "cross-env METAMASK_ENV=test mocha --watch --require test/setup.js --reporter min --recursive \"test/unit/**/*.js\" \"ui/app/**/*.test.js\"", "devtools:react": "react-devtools", @@ -90,7 +94,7 @@ "eth-block-tracker": "^4.4.2", "eth-contract-metadata": "^1.13.0", "eth-ens-namehash": "^2.0.8", - "eth-json-rpc-errors": "^1.1.0", + "eth-json-rpc-errors": "^2.0.0", "eth-json-rpc-filters": "^4.1.1", "eth-json-rpc-infura": "^4.0.1", "eth-json-rpc-middleware": "^4.4.0", @@ -113,6 +117,7 @@ "ethjs-query": "^0.3.4", "extension-port-stream": "^1.0.0", "extensionizer": "^1.0.1", + "fast-deep-equal": "^2.0.1", "fast-json-patch": "^2.0.4", "fuse.js": "^3.2.0", "gaba": "^1.9.3", @@ -125,10 +130,11 @@ "lodash.shuffle": "^4.2.0", "loglevel": "^1.4.1", "luxon": "^1.8.2", - "metamask-inpage-provider": "^3.0.0", + "metamask-inpage-provider": "^4.0.2", "metamask-logo": "^2.1.4", "mkdirp": "^0.5.1", "multihashes": "^0.4.12", + "nanoid": "^2.1.6", "nonce-tracker": "^1.0.0", "number-to-bn": "^1.7.0", "obj-multiplex": "^1.0.0", @@ -170,7 +176,9 @@ "redux-thunk": "^2.2.0", "request-promise": "^4.2.1", "reselect": "^3.0.1", + "rpc-cap": "^1.0.1", "safe-event-emitter": "^1.0.1", + "safe-json-stringify": "^1.2.0", "single-call-balance-checker-abi": "^1.0.0", "string.prototype.matchall": "^3.0.1", "swappable-obj-proxy": "^1.1.0", @@ -189,6 +197,8 @@ "@babel/preset-env": "^7.5.5", "@babel/preset-react": "^7.0.0", "@babel/register": "^7.5.5", + "@metamask/forwarder": "^1.0.0", + "@metamask/onboarding": "^0.1.2", "@sentry/cli": "^1.30.3", "@storybook/addon-actions": "^5.2.6", "@storybook/addon-info": "^5.1.1", @@ -201,6 +211,7 @@ "browserify": "^16.2.3", "browserify-transform-tools": "^1.7.0", "chai": "^4.1.0", + "chalk": "^2.4.2", "chromedriver": "^2.41.0", "concurrently": "^4.1.1", "coveralls": "^3.0.0", @@ -279,12 +290,12 @@ "rimraf": "^2.6.2", "sass-loader": "^7.0.1", "selenium-webdriver": "^3.5.0", + "serve-handler": "^6.1.2", "sesify": "^4.2.1", "sesify-viz": "^3.0.5", "sinon": "^5.0.0", "source-map": "^0.7.2", "source-map-explorer": "^2.0.1", - "static-server": "^2.2.1", "style-loader": "^0.21.0", "stylelint": "^9.10.1", "stylelint-config-standard": "^18.2.0", diff --git a/test/e2e/contract-test/contract.js b/test/e2e/contract-test/contract.js index ebfea34ec..6c3941b9d 100644 --- a/test/e2e/contract-test/contract.js +++ b/test/e2e/contract-test/contract.js @@ -1,4 +1,4 @@ -/*global ethereum*/ +/*global ethereum, MetamaskOnboarding */ /* The `piggybankContract` is compiled from: @@ -30,8 +30,14 @@ The `piggybankContract` is compiled from: } */ -web3.currentProvider.enable().then(() => { - var piggybankContract = web3.eth.contract([{'constant': false, 'inputs': [{'name': 'withdrawAmount', 'type': 'uint256'}], 'name': 'withdraw', 'outputs': [{'name': 'remainingBal', 'type': 'uint256'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'owner', 'outputs': [{'name': '', 'type': 'address'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [], 'name': 'deposit', 'outputs': [{'name': '', 'type': 'uint256'}], 'payable': true, 'stateMutability': 'payable', 'type': 'function'}, {'inputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'constructor'}]) +const forwarderOrigin = 'http://localhost:9010' + +const isMetaMaskInstalled = () => { + return Boolean(window.ethereum && window.ethereum.isMetaMask) +} + +const initialize = () => { + const onboardButton = document.getElementById('connectButton') const deployButton = document.getElementById('deployButton') const depositButton = document.getElementById('depositButton') const withdrawButton = document.getElementById('withdrawButton') @@ -42,200 +48,307 @@ web3.currentProvider.enable().then(() => { const transferTokensWithoutGas = document.getElementById('transferTokensWithoutGas') const approveTokensWithoutGas = document.getElementById('approveTokensWithoutGas') const signTypedData = document.getElementById('signTypedData') + const signTypedDataResults = document.getElementById('signTypedDataResult') + const getAccountsButton = document.getElementById('getAccounts') + const getAccountsResults = document.getElementById('getAccountsResult') - deployButton.addEventListener('click', async function () { - document.getElementById('contractStatus').innerHTML = 'Deploying' - - var piggybank = await piggybankContract.new( - { - from: web3.eth.accounts[0], - data: '0x608060405234801561001057600080fd5b5033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000808190555061023b806100686000396000f300608060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632e1a7d4d1461005c5780638da5cb5b1461009d578063d0e30db0146100f4575b600080fd5b34801561006857600080fd5b5061008760048036038101908080359060200190929190505050610112565b6040518082815260200191505060405180910390f35b3480156100a957600080fd5b506100b26101d0565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6100fc6101f6565b6040518082815260200191505060405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561017057600080fd5b8160008082825403925050819055503373ffffffffffffffffffffffffffffffffffffffff166108fc839081150290604051600060405180830381858888f193505050501580156101c5573d6000803e3d6000fd5b506000549050919050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60003460008082825401925050819055506000549050905600a165627a7a72305820f237db3ec816a52589d82512117bc85bc08d3537683ffeff9059108caf3e5d400029', - gas: '4700000', - }, function (e, contract) { - if (e) { - throw e - } - if (typeof contract.address !== 'undefined') { - console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash) - - document.getElementById('contractStatus').innerHTML = 'Deployed' - - depositButton.addEventListener('click', function () { - document.getElementById('contractStatus').innerHTML = 'Deposit initiated' - contract.deposit({ from: web3.eth.accounts[0], value: '0x3782dace9d900000' }, function (result) { - console.log(result) - document.getElementById('contractStatus').innerHTML = 'Deposit completed' - }) - }) - - withdrawButton.addEventListener('click', function () { - contract.withdraw('0xde0b6b3a7640000', { from: web3.eth.accounts[0] }, function (result) { - console.log(result) - document.getElementById('contractStatus').innerHTML = 'Withdrawn' - }) - }) - } - }) - - console.log(piggybank) - }) - - sendButton.addEventListener('click', function () { - web3.eth.sendTransaction({ - from: web3.eth.accounts[0], - to: '0x2f318C334780961FB129D2a6c30D0763d9a5C970', - value: '0x29a2241af62c0000', - gas: 21000, - gasPrice: 20000000000, - }, (result) => { - console.log(result) - }) - }) - - - createToken.addEventListener('click', async function () { - var _initialAmount = 100 - var _tokenName = 'TST' - var _decimalUnits = 0 - var _tokenSymbol = 'TST' - var humanstandardtokenContract = web3.eth.contract([{'constant': true, 'inputs': [], 'name': 'name', 'outputs': [{'name': '', 'type': 'string'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [{'name': '_spender', 'type': 'address'}, {'name': '_value', 'type': 'uint256'}], 'name': 'approve', 'outputs': [{'name': 'success', 'type': 'bool'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'totalSupply', 'outputs': [{'name': '', 'type': 'uint256'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [{'name': '_from', 'type': 'address'}, {'name': '_to', 'type': 'address'}, {'name': '_value', 'type': 'uint256'}], 'name': 'transferFrom', 'outputs': [{'name': 'success', 'type': 'bool'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'decimals', 'outputs': [{'name': '', 'type': 'uint8'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'version', 'outputs': [{'name': '', 'type': 'string'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': true, 'inputs': [{'name': '_owner', 'type': 'address'}], 'name': 'balanceOf', 'outputs': [{'name': 'balance', 'type': 'uint256'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'symbol', 'outputs': [{'name': '', 'type': 'string'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [{'name': '_to', 'type': 'address'}, {'name': '_value', 'type': 'uint256'}], 'name': 'transfer', 'outputs': [{'name': 'success', 'type': 'bool'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': false, 'inputs': [{'name': '_spender', 'type': 'address'}, {'name': '_value', 'type': 'uint256'}, {'name': '_extraData', 'type': 'bytes'}], 'name': 'approveAndCall', 'outputs': [{'name': 'success', 'type': 'bool'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [{'name': '_owner', 'type': 'address'}, {'name': '_spender', 'type': 'address'}], 'name': 'allowance', 'outputs': [{'name': 'remaining', 'type': 'uint256'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'inputs': [{'name': '_initialAmount', 'type': 'uint256'}, {'name': '_tokenName', 'type': 'string'}, {'name': '_decimalUnits', 'type': 'uint8'}, {'name': '_tokenSymbol', 'type': 'string'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'constructor'}, {'payable': false, 'stateMutability': 'nonpayable', 'type': 'fallback'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': '_from', 'type': 'address'}, {'indexed': true, 'name': '_to', 'type': 'address'}, {'indexed': false, 'name': '_value', 'type': 'uint256'}], 'name': 'Transfer', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': '_owner', 'type': 'address'}, {'indexed': true, 'name': '_spender', 'type': 'address'}, {'indexed': false, 'name': '_value', 'type': 'uint256'}], 'name': 'Approval', 'type': 'event'}]) - return humanstandardtokenContract.new( - _initialAmount, - _tokenName, - _decimalUnits, - _tokenSymbol, - { - from: web3.eth.accounts[0], - data: '0x60806040523480156200001157600080fd5b506040516200156638038062001566833981018060405260808110156200003757600080fd5b8101908080516401000000008111156200005057600080fd5b828101905060208101848111156200006757600080fd5b81518560018202830111640100000000821117156200008557600080fd5b50509291906020018051640100000000811115620000a257600080fd5b82810190506020810184811115620000b957600080fd5b8151856001820283011164010000000082111715620000d757600080fd5b5050929190602001805190602001909291908051906020019092919050505083838382600390805190602001906200011192919062000305565b5081600490805190602001906200012a92919062000305565b5080600560006101000a81548160ff021916908360ff1602179055505050506200016433826200016e640100000000026401000000009004565b50505050620003b4565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614151515620001ab57600080fd5b620001d081600254620002e36401000000000262001155179091906401000000009004565b60028190555062000237816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054620002e36401000000000262001155179091906401000000009004565b6000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a35050565b6000808284019050838110151515620002fb57600080fd5b8091505092915050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106200034857805160ff191683800117855562000379565b8280016001018555821562000379579182015b82811115620003785782518255916020019190600101906200035b565b5b5090506200038891906200038c565b5090565b620003b191905b80821115620003ad57600081600090555060010162000393565b5090565b90565b6111a280620003c46000396000f3fe6080604052600436106100af576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100b4578063095ea7b31461014457806318160ddd146101b757806323b872dd146101e2578063313ce5671461027557806339509351146102a657806370a082311461031957806395d89b411461037e578063a457c2d71461040e578063a9059cbb14610481578063dd62ed3e146104f4575b600080fd5b3480156100c057600080fd5b506100c9610579565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101095780820151818401526020810190506100ee565b50505050905090810190601f1680156101365780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561015057600080fd5b5061019d6004803603604081101561016757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061061b565b604051808215151515815260200191505060405180910390f35b3480156101c357600080fd5b506101cc610748565b6040518082815260200191505060405180910390f35b3480156101ee57600080fd5b5061025b6004803603606081101561020557600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610752565b604051808215151515815260200191505060405180910390f35b34801561028157600080fd5b5061028a61095a565b604051808260ff1660ff16815260200191505060405180910390f35b3480156102b257600080fd5b506102ff600480360360408110156102c957600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610971565b604051808215151515815260200191505060405180910390f35b34801561032557600080fd5b506103686004803603602081101561033c57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610ba8565b6040518082815260200191505060405180910390f35b34801561038a57600080fd5b50610393610bf0565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156103d35780820151818401526020810190506103b8565b50505050905090810190601f1680156104005780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561041a57600080fd5b506104676004803603604081101561043157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610c92565b604051808215151515815260200191505060405180910390f35b34801561048d57600080fd5b506104da600480360360408110156104a457600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610ec9565b604051808215151515815260200191505060405180910390f35b34801561050057600080fd5b506105636004803603604081101561051757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610ee0565b6040518082815260200191505060405180910390f35b606060038054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156106115780601f106105e657610100808354040283529160200191610611565b820191906000526020600020905b8154815290600101906020018083116105f457829003601f168201915b5050505050905090565b60008073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415151561065857600080fd5b81600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b6000600254905090565b60006107e382600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610f6790919063ffffffff16565b600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555061086e848484610f89565b3373ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925600160008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546040518082815260200191505060405180910390a3600190509392505050565b6000600560009054906101000a900460ff16905090565b60008073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141515156109ae57600080fd5b610a3d82600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205461115590919063ffffffff16565b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546040518082815260200191505060405180910390a36001905092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b606060048054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610c885780601f10610c5d57610100808354040283529160200191610c88565b820191906000526020600020905b815481529060010190602001808311610c6b57829003601f168201915b5050505050905090565b60008073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614151515610ccf57600080fd5b610d5e82600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610f6790919063ffffffff16565b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546040518082815260200191505060405180910390a36001905092915050565b6000610ed6338484610f89565b6001905092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b6000828211151515610f7857600080fd5b600082840390508091505092915050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614151515610fc557600080fd5b611016816000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610f6790919063ffffffff16565b6000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055506110a9816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205461115590919063ffffffff16565b6000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a3505050565b600080828401905083811015151561116c57600080fd5b809150509291505056fea165627a7a723058205fcdfea06f4d97b442bc9f444b1e92524bc66398eb4f37ed5a99f2093a8842640029000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000186a00000000000000000000000000000000000000000000000000000000000000003545354000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035453540000000000000000000000000000000000000000000000000000000000', - gas: '4700000', - gasPrice: '20000000000', - }, function (e, contract) { - console.log(e, contract) - if (typeof contract.address !== 'undefined') { - console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash) - - document.getElementById('tokenAddress').innerHTML = contract.address - - transferTokens.addEventListener('click', function (event) { - console.log(`event`, event) - contract.transfer('0x2f318C334780961FB129D2a6c30D0763d9a5C970', '15000', { - from: web3.eth.accounts[0], - to: contract.address, - data: '0xa9059cbb0000000000000000000000002f318c334780961fb129d2a6c30d0763d9a5c9700000000000000000000000000000000000000000000000000000000000003a98', - gas: 60000, - gasPrice: '20000000000', - }, function (result) { - console.log('result', result) - }) - }) - - approveTokens.addEventListener('click', function () { - contract.approve('0x9bc5baF874d2DA8D216aE9f137804184EE5AfEF4', '70000', { - from: web3.eth.accounts[0], - to: contract.address, - data: '0x095ea7b30000000000000000000000009bc5baF874d2DA8D216aE9f137804184EE5AfEF40000000000000000000000000000000000000000000000000000000000000005', - gas: 60000, - gasPrice: '20000000000', - }, function (result) { - console.log(result) - }) - }) - - transferTokensWithoutGas.addEventListener('click', function (event) { - console.log(`event`, event) - contract.transfer('0x2f318C334780961FB129D2a6c30D0763d9a5C970', '15000', { - from: web3.eth.accounts[0], - to: contract.address, - data: '0xa9059cbb0000000000000000000000002f318c334780961fb129d2a6c30d0763d9a5c9700000000000000000000000000000000000000000000000000000000000003a98', - gasPrice: '20000000000', - }, function (result) { - console.log('result', result) - }) - }) - - approveTokensWithoutGas.addEventListener('click', function () { - contract.approve('0x2f318C334780961FB129D2a6c30D0763d9a5C970', '70000', { - from: web3.eth.accounts[0], - to: contract.address, - data: '0x095ea7b30000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C9700000000000000000000000000000000000000000000000000000000000000005', - gasPrice: '20000000000', - }, function (result) { - console.log(result) - }) - }) - } - }) - - }) - - ethereum.autoRefreshOnNetworkChange = false - + const contractStatus = document.getElementById('contractStatus') + const tokenAddress = document.getElementById('tokenAddress') const networkDiv = document.getElementById('network') const chainIdDiv = document.getElementById('chainId') const accountsDiv = document.getElementById('accounts') - ethereum.on('networkChanged', (networkId) => { - networkDiv.innerHTML = networkId - }) + let onboarding + try { + onboarding = new MetamaskOnboarding({ forwarderOrigin }) + } catch (error) { + console.error(error) + } + let accounts + let piggybankContract - ethereum.on('chainIdChanged', (chainId) => { - chainIdDiv.innerHTML = chainId - }) + const accountButtons = [ + deployButton, + depositButton, + withdrawButton, + sendButton, + createToken, + transferTokens, + approveTokens, + transferTokensWithoutGas, + approveTokensWithoutGas, + ] - ethereum.on('accountsChanged', (accounts) => { - accountsDiv.innerHTML = accounts - }) + for (const button of accountButtons) { + button.disabled = true + } - const signTypedDataResultsDiv = document.getElementById('signTypedDataResult') - signTypedData.addEventListener('click', function () { + const isMetaMaskConnected = () => accounts && accounts.length > 0 - const typedData = { - types: { - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'version', type: 'string' }, - { name: 'chainId', type: 'uint256' }, - { name: 'verifyingContract', type: 'address' }, - ], - Person: [ - { name: 'name', type: 'string' }, - { name: 'wallet', type: 'address' }, - ], - Mail: [ - { name: 'from', type: 'Person' }, - { name: 'to', type: 'Person' }, - { name: 'contents', type: 'string' }, - ], - }, - primaryType: 'Mail', - domain: { - name: 'Ether Mail', - version: '1', - chainId: 3, - verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', - }, - message: { - sender: { - name: 'Cow', - wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', - }, - recipient: { - name: 'Bob', - wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', - }, - contents: 'Hello, Bob!', - }, + const onClickInstall = () => { + onboardButton.innerText = 'Onboarding in progress' + onboardButton.disabled = true + onboarding.startOnboarding() + } + + const onClickConnect = async () => { + await window.ethereum.enable() + } + + const updateButtons = () => { + const accountButtonsDisabled = !isMetaMaskInstalled() || !isMetaMaskConnected() + if (accountButtonsDisabled) { + for (const button of accountButtons) { + button.disabled = true + } + } else { + deployButton.disabled = false + sendButton.disabled = false + createToken.disabled = false } - web3.currentProvider.sendAsync({ - method: 'eth_signTypedData_v3', - params: [ethereum.selectedAddress, JSON.stringify(typedData)], - from: ethereum.selectedAddress, - }, function (err, result) { - if (err) { - console.log(err) - } else { - signTypedDataResultsDiv.innerHTML = result + + if (!isMetaMaskInstalled()) { + onboardButton.innerText = 'Click here to install MetaMask!' + onboardButton.onclick = onClickInstall + } else if (isMetaMaskConnected()) { + onboardButton.innerText = 'Connected' + onboardButton.disabled = true + if (onboarding) { + onboarding.stopOnboarding() + } + } else { + onboardButton.innerText = 'Connect' + onboardButton.onclick = onClickConnect + } + } + + const initializeAccountButtons = () => { + piggybankContract = web3.eth.contract([{'constant': false, 'inputs': [{'name': 'withdrawAmount', 'type': 'uint256'}], 'name': 'withdraw', 'outputs': [{'name': 'remainingBal', 'type': 'uint256'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'owner', 'outputs': [{'name': '', 'type': 'address'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [], 'name': 'deposit', 'outputs': [{'name': '', 'type': 'uint256'}], 'payable': true, 'stateMutability': 'payable', 'type': 'function'}, {'inputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'constructor'}]) + deployButton.onclick = async () => { + contractStatus.innerHTML = 'Deploying' + + const piggybank = await piggybankContract.new( + { + from: accounts[0], + data: '0x608060405234801561001057600080fd5b5033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000808190555061023b806100686000396000f300608060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632e1a7d4d1461005c5780638da5cb5b1461009d578063d0e30db0146100f4575b600080fd5b34801561006857600080fd5b5061008760048036038101908080359060200190929190505050610112565b6040518082815260200191505060405180910390f35b3480156100a957600080fd5b506100b26101d0565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6100fc6101f6565b6040518082815260200191505060405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561017057600080fd5b8160008082825403925050819055503373ffffffffffffffffffffffffffffffffffffffff166108fc839081150290604051600060405180830381858888f193505050501580156101c5573d6000803e3d6000fd5b506000549050919050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60003460008082825401925050819055506000549050905600a165627a7a72305820f237db3ec816a52589d82512117bc85bc08d3537683ffeff9059108caf3e5d400029', + gas: '4700000', + }, (error, contract) => { + if (error) { + contractStatus.innerHTML = 'Deployment Failed' + throw error + } else if (contract.address === undefined) { + return + } + + console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash) + contractStatus.innerHTML = 'Deployed' + depositButton.disabled = false + withdrawButton.disabled = false + + depositButton.onclick = () => { + contractStatus.innerHTML = 'Deposit initiated' + contract.deposit( + { + from: accounts[0], + value: '0x3782dace9d900000', + }, + (result) => { + console.log(result) + contractStatus.innerHTML = 'Deposit completed' + } + ) + } + withdrawButton.onclick = () => { + contract.withdraw( + '0xde0b6b3a7640000', + { from: accounts[0] }, + (result) => { + console.log(result) + contractStatus.innerHTML = 'Withdrawn' + } + ) + } + } + ) + console.log(piggybank) + } + + sendButton.onclick = () => { + web3.eth.sendTransaction({ + from: accounts[0], + to: '0x2f318C334780961FB129D2a6c30D0763d9a5C970', + value: '0x29a2241af62c0000', + gas: 21000, + gasPrice: 20000000000, + }, (result) => { + console.log(result) + }) + } + + createToken.onclick = async () => { + const _initialAmount = 100 + const _tokenName = 'TST' + const _decimalUnits = 0 + const _tokenSymbol = 'TST' + const humanstandardtokenContract = web3.eth.contract([{'constant': true, 'inputs': [], 'name': 'name', 'outputs': [{'name': '', 'type': 'string'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [{'name': '_spender', 'type': 'address'}, {'name': '_value', 'type': 'uint256'}], 'name': 'approve', 'outputs': [{'name': 'success', 'type': 'bool'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'totalSupply', 'outputs': [{'name': '', 'type': 'uint256'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [{'name': '_from', 'type': 'address'}, {'name': '_to', 'type': 'address'}, {'name': '_value', 'type': 'uint256'}], 'name': 'transferFrom', 'outputs': [{'name': 'success', 'type': 'bool'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'decimals', 'outputs': [{'name': '', 'type': 'uint8'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'version', 'outputs': [{'name': '', 'type': 'string'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': true, 'inputs': [{'name': '_owner', 'type': 'address'}], 'name': 'balanceOf', 'outputs': [{'name': 'balance', 'type': 'uint256'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'symbol', 'outputs': [{'name': '', 'type': 'string'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [{'name': '_to', 'type': 'address'}, {'name': '_value', 'type': 'uint256'}], 'name': 'transfer', 'outputs': [{'name': 'success', 'type': 'bool'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': false, 'inputs': [{'name': '_spender', 'type': 'address'}, {'name': '_value', 'type': 'uint256'}, {'name': '_extraData', 'type': 'bytes'}], 'name': 'approveAndCall', 'outputs': [{'name': 'success', 'type': 'bool'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [{'name': '_owner', 'type': 'address'}, {'name': '_spender', 'type': 'address'}], 'name': 'allowance', 'outputs': [{'name': 'remaining', 'type': 'uint256'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'inputs': [{'name': '_initialAmount', 'type': 'uint256'}, {'name': '_tokenName', 'type': 'string'}, {'name': '_decimalUnits', 'type': 'uint8'}, {'name': '_tokenSymbol', 'type': 'string'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'constructor'}, {'payable': false, 'stateMutability': 'nonpayable', 'type': 'fallback'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': '_from', 'type': 'address'}, {'indexed': true, 'name': '_to', 'type': 'address'}, {'indexed': false, 'name': '_value', 'type': 'uint256'}], 'name': 'Transfer', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': '_owner', 'type': 'address'}, {'indexed': true, 'name': '_spender', 'type': 'address'}, {'indexed': false, 'name': '_value', 'type': 'uint256'}], 'name': 'Approval', 'type': 'event'}]) + + return humanstandardtokenContract.new( + _initialAmount, + _tokenName, + _decimalUnits, + _tokenSymbol, + { + from: accounts[0], + data: '0x60806040523480156200001157600080fd5b506040516200156638038062001566833981018060405260808110156200003757600080fd5b8101908080516401000000008111156200005057600080fd5b828101905060208101848111156200006757600080fd5b81518560018202830111640100000000821117156200008557600080fd5b50509291906020018051640100000000811115620000a257600080fd5b82810190506020810184811115620000b957600080fd5b8151856001820283011164010000000082111715620000d757600080fd5b5050929190602001805190602001909291908051906020019092919050505083838382600390805190602001906200011192919062000305565b5081600490805190602001906200012a92919062000305565b5080600560006101000a81548160ff021916908360ff1602179055505050506200016433826200016e640100000000026401000000009004565b50505050620003b4565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614151515620001ab57600080fd5b620001d081600254620002e36401000000000262001155179091906401000000009004565b60028190555062000237816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054620002e36401000000000262001155179091906401000000009004565b6000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a35050565b6000808284019050838110151515620002fb57600080fd5b8091505092915050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106200034857805160ff191683800117855562000379565b8280016001018555821562000379579182015b82811115620003785782518255916020019190600101906200035b565b5b5090506200038891906200038c565b5090565b620003b191905b80821115620003ad57600081600090555060010162000393565b5090565b90565b6111a280620003c46000396000f3fe6080604052600436106100af576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100b4578063095ea7b31461014457806318160ddd146101b757806323b872dd146101e2578063313ce5671461027557806339509351146102a657806370a082311461031957806395d89b411461037e578063a457c2d71461040e578063a9059cbb14610481578063dd62ed3e146104f4575b600080fd5b3480156100c057600080fd5b506100c9610579565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101095780820151818401526020810190506100ee565b50505050905090810190601f1680156101365780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561015057600080fd5b5061019d6004803603604081101561016757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061061b565b604051808215151515815260200191505060405180910390f35b3480156101c357600080fd5b506101cc610748565b6040518082815260200191505060405180910390f35b3480156101ee57600080fd5b5061025b6004803603606081101561020557600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610752565b604051808215151515815260200191505060405180910390f35b34801561028157600080fd5b5061028a61095a565b604051808260ff1660ff16815260200191505060405180910390f35b3480156102b257600080fd5b506102ff600480360360408110156102c957600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610971565b604051808215151515815260200191505060405180910390f35b34801561032557600080fd5b506103686004803603602081101561033c57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610ba8565b6040518082815260200191505060405180910390f35b34801561038a57600080fd5b50610393610bf0565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156103d35780820151818401526020810190506103b8565b50505050905090810190601f1680156104005780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561041a57600080fd5b506104676004803603604081101561043157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610c92565b604051808215151515815260200191505060405180910390f35b34801561048d57600080fd5b506104da600480360360408110156104a457600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610ec9565b604051808215151515815260200191505060405180910390f35b34801561050057600080fd5b506105636004803603604081101561051757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610ee0565b6040518082815260200191505060405180910390f35b606060038054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156106115780601f106105e657610100808354040283529160200191610611565b820191906000526020600020905b8154815290600101906020018083116105f457829003601f168201915b5050505050905090565b60008073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415151561065857600080fd5b81600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b6000600254905090565b60006107e382600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610f6790919063ffffffff16565b600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555061086e848484610f89565b3373ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925600160008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546040518082815260200191505060405180910390a3600190509392505050565b6000600560009054906101000a900460ff16905090565b60008073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141515156109ae57600080fd5b610a3d82600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205461115590919063ffffffff16565b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546040518082815260200191505060405180910390a36001905092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b606060048054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610c885780601f10610c5d57610100808354040283529160200191610c88565b820191906000526020600020905b815481529060010190602001808311610c6b57829003601f168201915b5050505050905090565b60008073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614151515610ccf57600080fd5b610d5e82600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610f6790919063ffffffff16565b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546040518082815260200191505060405180910390a36001905092915050565b6000610ed6338484610f89565b6001905092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b6000828211151515610f7857600080fd5b600082840390508091505092915050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614151515610fc557600080fd5b611016816000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610f6790919063ffffffff16565b6000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055506110a9816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205461115590919063ffffffff16565b6000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a3505050565b600080828401905083811015151561116c57600080fd5b809150509291505056fea165627a7a723058205fcdfea06f4d97b442bc9f444b1e92524bc66398eb4f37ed5a99f2093a8842640029000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000186a00000000000000000000000000000000000000000000000000000000000000003545354000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035453540000000000000000000000000000000000000000000000000000000000', + gas: '4700000', + gasPrice: '20000000000', + }, (error, contract) => { + if (error) { + tokenAddress.innerHTML = 'Creation Failed' + throw error + } else if (contract.address === undefined) { + return + } + + console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash) + tokenAddress.innerHTML = contract.address + transferTokens.disabled = false + approveTokens.disabled = false + transferTokensWithoutGas.disabled = false + approveTokensWithoutGas.disabled = false + + transferTokens.onclick = (event) => { + console.log(`event`, event) + contract.transfer('0x2f318C334780961FB129D2a6c30D0763d9a5C970', '15000', { + from: accounts[0], + to: contract.address, + data: '0xa9059cbb0000000000000000000000002f318c334780961fb129d2a6c30d0763d9a5c9700000000000000000000000000000000000000000000000000000000000003a98', + gas: 60000, + gasPrice: '20000000000', + }, (result) => { + console.log('result', result) + }) + } + + approveTokens.onclick = () => { + contract.approve('0x9bc5baF874d2DA8D216aE9f137804184EE5AfEF4', '70000', { + from: accounts[0], + to: contract.address, + data: '0x095ea7b30000000000000000000000009bc5baF874d2DA8D216aE9f137804184EE5AfEF40000000000000000000000000000000000000000000000000000000000000005', + gas: 60000, + gasPrice: '20000000000', + }, (result) => { + console.log(result) + }) + } + + transferTokensWithoutGas.onclick = (event) => { + console.log(`event`, event) + contract.transfer('0x2f318C334780961FB129D2a6c30D0763d9a5C970', '15000', { + from: accounts[0], + to: contract.address, + data: '0xa9059cbb0000000000000000000000002f318c334780961fb129d2a6c30d0763d9a5c9700000000000000000000000000000000000000000000000000000000000003a98', + gasPrice: '20000000000', + }, (result) => { + console.log('result', result) + }) + } + + approveTokensWithoutGas.onclick = () => { + contract.approve('0x2f318C334780961FB129D2a6c30D0763d9a5C970', '70000', { + from: accounts[0], + to: contract.address, + data: '0x095ea7b30000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C9700000000000000000000000000000000000000000000000000000000000000005', + gasPrice: '20000000000', + }, (result) => { + console.log(result) + }) + } + } + ) + } + + signTypedData.addEventListener('click', () => { + const typedData = { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], + }, + primaryType: 'Mail', + domain: { + name: 'Ether Mail', + version: '1', + chainId: 3, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + }, + message: { + sender: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + recipient: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, + } + web3.currentProvider.sendAsync({ + method: 'eth_signTypedData_v3', + params: [ethereum.selectedAddress, JSON.stringify(typedData)], + from: ethereum.selectedAddress, + }, (err, result) => { + if (err) { + console.log(err) + } else { + signTypedDataResults.innerHTML = result + } + }) + }) + + getAccountsButton.addEventListener('click', async () => { + try { + const accounts = await ethereum.send({ method: 'eth_accounts' }) + getAccountsResults.innerHTML = accounts[0] || 'Not able to get accounts' + } catch (error) { + console.error(error) + getAccountsResults.innerHTML = `Error: ${error}` } }) - }) -}) + + } + + updateButtons() + if (isMetaMaskInstalled()) { + ethereum.autoRefreshOnNetworkChange = false + ethereum.on('networkChanged', (networkId) => { + networkDiv.innerHTML = networkId + }) + ethereum.on('chainIdChanged', (chainId) => { + chainIdDiv.innerHTML = chainId + }) + ethereum.on('accountsChanged', (newAccounts) => { + const connecting = Boolean((!accounts || !accounts.length) && newAccounts && newAccounts.length) + accounts = newAccounts + accountsDiv.innerHTML = accounts + if (connecting) { + initializeAccountButtons() + } + updateButtons() + }) + } +} +window.addEventListener('DOMContentLoaded', initialize) diff --git a/test/e2e/contract-test/index.html b/test/e2e/contract-test/index.html index 9689654ee..7655b8786 100644 --- a/test/e2e/contract-test/index.html +++ b/test/e2e/contract-test/index.html @@ -1,51 +1,69 @@ + E2E Test Dapp + + -
    -
    Contract
    -
    - - - -
    -
    - Not clicked -
    -
    -
    -
    Send eth
    -
    +
    +

    E2E Test Dapp

    +
    +
    +
    +

    Connect

    + +
    +
    +

    Contract

    +
    + + + +
    +
    + Contract Status: Not clicked +
    +
    +
    +

    Send Eth

    -
    -
    -
    -
    Send tokens
    -
    -
    - - - - - -
    -
    -
    -
    Network:
    -
    ChainId:
    -
    Accounts:
    -
    -
    -
    -
    Sign Typed Data
    -
    + +
    +

    Send Tokens

    +
    + Token: +
    +
    + + + + + +
    +
    +
    +

    Get Accounts

    + +
    +
    +
    +

    Status

    +
    + Network: +
    +
    + ChainId: +
    +
    + Accounts: +
    +
    +
    +

    Sign Typed Data

    -
    Sign Typed Data Result:
    -
    -
    - - +
    Sign Typed Data Result:
    + + - - \ No newline at end of file + diff --git a/test/e2e/ethereum-on.spec.js b/test/e2e/ethereum-on.spec.js index 9c6cd4ceb..c50615521 100644 --- a/test/e2e/ethereum-on.spec.js +++ b/test/e2e/ethereum-on.spec.js @@ -113,28 +113,42 @@ describe('MetaMask', function () { let extension let popup let dapp - it('switches to a dapp', async () => { + + it('connects to the dapp', async () => { await openNewPage(driver, 'http://127.0.0.1:8080/') await delay(regularDelayMs) + const connectButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`)) + await connectButton.click() + + await delay(regularDelayMs) + await waitUntilXWindowHandles(driver, 3) const windowHandles = await driver.getAllWindowHandles() extension = windowHandles[0] - popup = await switchToWindowWithTitle(driver, 'MetaMask Notification', windowHandles) - dapp = windowHandles.find(handle => handle !== extension && handle !== popup) + dapp = await switchToWindowWithTitle(driver, 'E2E Test Dapp', windowHandles) + popup = windowHandles.find(handle => handle !== extension && handle !== dapp) + + await driver.switchTo().window(popup) await delay(regularDelayMs) - const approveButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`)) - await approveButton.click() + const accountButton = await findElement(driver, By.css('.permissions-connect-choose-account__account')) + await accountButton.click() + + const submitButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Submit')]`)) + await submitButton.click() + + await waitUntilXWindowHandles(driver, 2) await driver.switchTo().window(dapp) await delay(regularDelayMs) }) - it('has not set the network within the dapp', async () => { + it('has the ganache network id within the dapp', async () => { const networkDiv = await findElement(driver, By.css('#network')) - assert.equal(await networkDiv.getText(), '') + await delay(regularDelayMs) + assert.equal(await networkDiv.getText(), '5777') }) it('changes the network', async () => { diff --git a/test/e2e/helpers.js b/test/e2e/helpers.js index 9e12ba8da..27de882eb 100644 --- a/test/e2e/helpers.js +++ b/test/e2e/helpers.js @@ -123,6 +123,8 @@ async function loadExtension (driver, extensionId) { await driver.get(`moz-extension://${extensionId}/home.html`) break } + default: + throw new Error('Unrecognized SELENIUM_BROWSER value') } } diff --git a/test/e2e/metamask-responsive-ui.spec.js b/test/e2e/metamask-responsive-ui.spec.js index 90cf35710..143e759ee 100644 --- a/test/e2e/metamask-responsive-ui.spec.js +++ b/test/e2e/metamask-responsive-ui.spec.js @@ -134,7 +134,7 @@ describe('MetaMask', function () { it('show account details dropdown menu', async () => { await driver.findElement(By.css('div.menu-bar__open-in-browser')).click() const options = await driver.findElements(By.css('div.menu.account-details-dropdown div.menu__item')) - assert.equal(options.length, 3) // HD Wallet type does not have to show the Remove Account option + assert.equal(options.length, 4) // HD Wallet type does not have to show the Remove Account option await delay(regularDelayMs) }) }) diff --git a/test/e2e/metamask-ui.spec.js b/test/e2e/metamask-ui.spec.js index 1ee01595b..ef02631c2 100644 --- a/test/e2e/metamask-ui.spec.js +++ b/test/e2e/metamask-ui.spec.js @@ -432,23 +432,35 @@ describe('MetaMask', function () { await delay(largeDelayMs) }) - it('starts a send transaction inside the dapp', async () => { + it('connects the dapp', async () => { await openNewPage(driver, 'http://127.0.0.1:8080/') await delay(regularDelayMs) + const connectButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`)) + await connectButton.click() + + await delay(regularDelayMs) + await waitUntilXWindowHandles(driver, 3) windowHandles = await driver.getAllWindowHandles() extension = windowHandles[0] - popup = await switchToWindowWithTitle(driver, 'MetaMask Notification', windowHandles) - dapp = windowHandles.find(handle => handle !== extension && handle !== popup) + dapp = await switchToWindowWithTitle(driver, 'E2E Test Dapp', windowHandles) + popup = windowHandles.find(handle => handle !== extension && handle !== dapp) + + await driver.switchTo().window(popup) await delay(regularDelayMs) - const approveButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`)) - await approveButton.click() + const accountButton = await findElement(driver, By.css('.permissions-connect-choose-account__account')) + await accountButton.click() + + const submitButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Submit')]`)) + await submitButton.click() + + await waitUntilXWindowHandles(driver, 2) await driver.switchTo().window(dapp) - await delay(2000) + await delay(regularDelayMs) }) it('initiates a send from the dapp', async () => { diff --git a/test/e2e/permissions.spec.js b/test/e2e/permissions.spec.js new file mode 100644 index 000000000..b7147d7a2 --- /dev/null +++ b/test/e2e/permissions.spec.js @@ -0,0 +1,201 @@ +const assert = require('assert') +const webdriver = require('selenium-webdriver') +const { By, until } = webdriver +const { + delay, +} = require('./func') +const { + checkBrowserForConsoleErrors, + findElement, + findElements, + openNewPage, + verboseReportOnFailure, + waitUntilXWindowHandles, + switchToWindowWithTitle, + setupFetchMocking, + prepareExtensionForTesting, +} = require('./helpers') +const enLocaleMessages = require('../../app/_locales/en/messages.json') + +describe('MetaMask', function () { + let driver + let publicAddress + + const tinyDelayMs = 200 + const regularDelayMs = tinyDelayMs * 2 + const largeDelayMs = regularDelayMs * 2 + + this.timeout(0) + this.bail(true) + + before(async function () { + const result = await prepareExtensionForTesting() + driver = result.driver + await setupFetchMocking(driver) + }) + + 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('Going through the first time flow, but skipping the seed phrase challenge', () => { + it('clicks the continue button on the welcome screen', async () => { + await findElement(driver, By.css('.welcome-page__header')) + const welcomeScreenBtn = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.getStarted.message}')]`)) + welcomeScreenBtn.click() + await delay(largeDelayMs) + }) + + it('clicks the "Create New Wallet" option', async () => { + const customRpcButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Create a 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('accepts a secure password', async () => { + const passwordBox = await findElement(driver, By.css('.first-time-flow__form #create-password')) + const passwordBoxConfirm = await findElement(driver, By.css('.first-time-flow__form #confirm-password')) + const button = await findElement(driver, By.css('.first-time-flow__form button')) + + await passwordBox.sendKeys('correct horse battery staple') + await passwordBoxConfirm.sendKeys('correct horse battery staple') + + const tosCheckBox = await findElement(driver, By.css('.first-time-flow__checkbox')) + await tosCheckBox.click() + + await button.click() + await delay(largeDelayMs) + }) + + it('skips the seed phrase challenge', async () => { + const button = await findElement(driver, By.xpath(`//button[contains(text(), '${enLocaleMessages.remindMeLater.message}')]`)) + await button.click() + await delay(regularDelayMs) + + const detailsButton = await findElement(driver, By.css('.account-details__details-button')) + await detailsButton.click() + await delay(regularDelayMs) + }) + + it('gets the current accounts address', async () => { + const addressInput = await findElement(driver, By.css('.qr-ellip-address')) + publicAddress = await addressInput.getAttribute('value') + const accountModal = await driver.findElement(By.css('span .modal')) + + await driver.executeScript("document.querySelector('.account-modal-close').click()") + + await driver.wait(until.stalenessOf(accountModal)) + await delay(regularDelayMs) + }) + }) + + describe('sets permissions', () => { + let extension + let popup + let dapp + + it('connects to the dapp', async () => { + await openNewPage(driver, 'http://127.0.0.1:8080/') + await delay(regularDelayMs) + + const connectButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`)) + await connectButton.click() + + await waitUntilXWindowHandles(driver, 3) + const windowHandles = await driver.getAllWindowHandles() + + extension = windowHandles[0] + dapp = await switchToWindowWithTitle(driver, 'E2E Test Dapp', windowHandles) + popup = windowHandles.find(handle => handle !== extension && handle !== dapp) + + await driver.switchTo().window(popup) + + await delay(regularDelayMs) + + const accountButton = await findElement(driver, By.css('.permissions-connect-choose-account__account')) + await accountButton.click() + + const submitButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Submit')]`)) + await submitButton.click() + + await waitUntilXWindowHandles(driver, 2) + await driver.switchTo().window(extension) + await delay(regularDelayMs) + }) + + it('shows connected sites', async () => { + const connectedSites = await findElement(driver, By.xpath(`//button[contains(text(), 'Connected Sites')]`)) + await connectedSites.click() + + await findElement(driver, By.css('.connected-sites__title')) + + const domains = await findElements(driver, By.css('.connected-sites-list__domain')) + assert.equal(domains.length, 1) + + const domainName = await findElement(driver, By.css('.connected-sites-list__domain-name')) + assert.equal(await domainName.getText(), 'E2E Test Dapp') + + await domains[0].click() + + const permissionDescription = await findElement(driver, By.css('.connected-sites-list__permission-description')) + assert.equal(await permissionDescription.getText(), 'View the address of the selected account') + }) + + it('can get accounts within the dapp', async () => { + await driver.switchTo().window(dapp) + await delay(regularDelayMs) + + const getAccountsButton = await findElement(driver, By.xpath(`//button[contains(text(), 'eth_accounts')]`)) + await getAccountsButton.click() + + const getAccountsResult = await findElement(driver, By.css('#getAccountsResult')) + assert.equal((await getAccountsResult.getText()).toLowerCase(), publicAddress.toLowerCase()) + }) + + it('can disconnect all accounts', async () => { + await driver.switchTo().window(extension) + + const disconnectAllButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Disconnect All')]`)) + await disconnectAllButton.click() + + const disconnectModal = await driver.findElement(By.css('span .modal')) + + const disconnectAllModalButton = await findElement(driver, By.css('.disconnect-all-modal .btn-danger')) + await disconnectAllModalButton.click() + + await driver.wait(until.stalenessOf(disconnectModal)) + await delay(regularDelayMs) + }) + + it('can no longer get accounts within the dapp', async () => { + await driver.switchTo().window(dapp) + await delay(regularDelayMs) + + const getAccountsButton = await findElement(driver, By.xpath(`//button[contains(text(), 'eth_accounts')]`)) + await getAccountsButton.click() + + const getAccountsResult = await findElement(driver, By.css('#getAccountsResult')) + assert.equal(await getAccountsResult.getText(), 'Not able to get accounts') + }) + }) +}) diff --git a/test/e2e/run-all.sh b/test/e2e/run-all.sh index 33c2428da..55a06fc88 100755 --- a/test/e2e/run-all.sh +++ b/test/e2e/run-all.sh @@ -60,6 +60,14 @@ concurrently --kill-others \ 'yarn dapp' \ 'sleep 5 && mocha test/e2e/ethereum-on.spec' +concurrently --kill-others \ + --names 'ganache,dapp,e2e' \ + --prefix '[{time}][{name}]' \ + --success first \ + 'yarn ganache:start' \ + 'yarn dapp' \ + 'sleep 5 && mocha test/e2e/permissions.spec' + export GANACHE_ARGS="${BASE_GANACHE_ARGS} --deterministic --account=0x250F458997A364988956409A164BA4E16F0F99F916ACDD73ADCD3A1DE30CF8D1,0 --account=0x53CB0AB5226EEBF4D872113D98332C1555DC304443BEE1CF759D15798D3C55A9,25000000000000000000" concurrently --kill-others \ --names 'ganache,sendwithprivatedapp,e2e' \ diff --git a/test/e2e/run-web3.sh b/test/e2e/run-web3.sh index 174370683..729333b84 100755 --- a/test/e2e/run-web3.sh +++ b/test/e2e/run-web3.sh @@ -9,5 +9,5 @@ export PATH="$PATH:./node_modules/.bin" concurrently --kill-others \ --names 'dapp,e2e' \ --prefix '[{time}][{name}]' \ - 'static-server test/web3 --port 8080' \ + 'node development/static-server.js test/web3 --port 8080' \ 'sleep 5 && mocha test/e2e/web3.spec' diff --git a/test/e2e/signature-request.spec.js b/test/e2e/signature-request.spec.js index e9490e08d..f36b6ac51 100644 --- a/test/e2e/signature-request.spec.js +++ b/test/e2e/signature-request.spec.js @@ -119,28 +119,40 @@ describe('MetaMask', function () { }) }) - describe('provider listening for events', () => { + describe('successfuly signs typed data', () => { let extension let popup let dapp let windowHandles - it('switches to a dapp', async () => { + + it('connects to the dapp', async () => { await openNewPage(driver, 'http://127.0.0.1:8080/') await delay(regularDelayMs) + const connectButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`)) + await connectButton.click() + + await delay(regularDelayMs) + await waitUntilXWindowHandles(driver, 3) - windowHandles = await driver.getAllWindowHandles() + const windowHandles = await driver.getAllWindowHandles() extension = windowHandles[0] - popup = await switchToWindowWithTitle(driver, 'MetaMask Notification', windowHandles) - dapp = windowHandles.find(handle => handle !== extension && handle !== popup) + dapp = await switchToWindowWithTitle(driver, 'E2E Test Dapp', windowHandles) + popup = windowHandles.find(handle => handle !== extension && handle !== dapp) + + await driver.switchTo().window(popup) await delay(regularDelayMs) - const approveButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`)) - await approveButton.click() + const accountButton = await findElement(driver, By.css('.permissions-connect-choose-account__account')) + await accountButton.click() + + const submitButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Submit')]`)) + await submitButton.click() + + await waitUntilXWindowHandles(driver, 2) await driver.switchTo().window(dapp) - await delay(regularDelayMs) }) it('creates a sign typed data signature request', async () => { @@ -148,6 +160,7 @@ describe('MetaMask', function () { await signTypedMessage.click() await delay(largeDelayMs) + await delay(regularDelayMs) windowHandles = await driver.getAllWindowHandles() await switchToWindowWithTitle(driver, 'MetaMask Notification', windowHandles) await delay(regularDelayMs) diff --git a/test/e2e/web3.spec.js b/test/e2e/web3.spec.js index 6dd081322..8c0853755 100644 --- a/test/e2e/web3.spec.js +++ b/test/e2e/web3.spec.js @@ -30,7 +30,7 @@ describe('Using MetaMask with an existing account', function () { await delay(largeDelayMs) const [results] = await findElements(driver, By.css('#results')) const resulttext = await results.getText() - var parsedData = JSON.parse(resulttext) + const parsedData = JSON.parse(resulttext) return (parsedData) @@ -126,6 +126,11 @@ describe('Using MetaMask with an existing account', function () { await openNewPage(driver, 'http://127.0.0.1:8080/') await delay(regularDelayMs) + const connectButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`)) + await connectButton.click() + + await delay(regularDelayMs) + await waitUntilXWindowHandles(driver, 3) const windowHandles = await driver.getAllWindowHandles() @@ -148,14 +153,14 @@ describe('Using MetaMask with an existing account', function () { it('testing hexa methods', async () => { - var List = await driver.findElements(By.className('hexaNumberMethods')) + const List = await driver.findElements(By.className('hexaNumberMethods')) for (let i = 0; i < List.length; i++) { try { - var parsedData = await button(List[i]) + const parsedData = await button(List[i]) console.log(parsedData) - var result = parseInt(parsedData.result, 16) + const result = parseInt(parsedData.result, 16) assert.equal((typeof result === 'number'), true) await delay(regularDelayMs) @@ -169,14 +174,14 @@ describe('Using MetaMask with an existing account', function () { it('testing booleanMethods', async () => { - var List = await driver.findElements(By.className('booleanMethods')) + const List = await driver.findElements(By.className('booleanMethods')) for (let i = 0; i < List.length; i++) { try { - var parsedData = await button(List[i]) + const parsedData = await button(List[i]) console.log(parsedData) - var result = parsedData.result + const result = parsedData.result assert.equal(result, false) await delay(regularDelayMs) @@ -192,16 +197,16 @@ describe('Using MetaMask with an existing account', function () { it('testing transactionMethods', async () => { - var List = await driver.findElements(By.className('transactionMethods')) + const List = await driver.findElements(By.className('transactionMethods')) for (let i = 0; i < List.length; i++) { try { - var parsedData = await button(List[i]) + const parsedData = await button(List[i]) console.log(parsedData.result.blockHash) - var result = [] + const result = [] result.push(parseInt(parsedData.result.blockHash, 16)) result.push(parseInt(parsedData.result.blockNumber, 16)) result.push(parseInt(parsedData.result.gas, 16)) @@ -234,17 +239,17 @@ describe('Using MetaMask with an existing account', function () { it('testing blockMethods', async () => { - var List = await driver.findElements(By.className('blockMethods')) + const List = await driver.findElements(By.className('blockMethods')) for (let i = 0; i < List.length; i++) { try { - var parsedData = await button(List[i]) + const parsedData = await button(List[i]) console.log(JSON.stringify(parsedData) + i) console.log(parsedData.result.parentHash) - var result = parseInt(parsedData.result.parentHash, 16) + const result = parseInt(parsedData.result.parentHash, 16) assert.equal((typeof result === 'number'), true) await delay(regularDelayMs) @@ -260,9 +265,9 @@ describe('Using MetaMask with an existing account', function () { it('testing methods', async () => { - var List = await driver.findElements(By.className('methods')) - var parsedData - var result + const List = await driver.findElements(By.className('methods')) + let parsedData + let result for (let i = 0; i < List.length; i++) { try { diff --git a/test/helper.js b/test/helper.js index ddc2aba40..bea0b326c 100644 --- a/test/helper.js +++ b/test/helper.js @@ -17,7 +17,7 @@ server.listen(8545, () => { }) // logging util -var log = require('loglevel') +const log = require('loglevel') log.setDefaultLevel(5) global.log = log @@ -36,8 +36,12 @@ require('jsdom-global')() window.localStorage = {} // crypto.getRandomValues -if (!window.crypto) window.crypto = {} -if (!window.crypto.getRandomValues) window.crypto.getRandomValues = require('polyfill-crypto.getrandomvalues') +if (!window.crypto) { + window.crypto = {} +} +if (!window.crypto.getRandomValues) { + window.crypto.getRandomValues = require('polyfill-crypto.getrandomvalues') +} function enableFailureOnUnhandledPromiseRejection () { // overwrite node's promise with the stricter Bluebird promise @@ -58,9 +62,11 @@ function enableFailureOnUnhandledPromiseRejection () { throw evt.detail.reason }) } else { - var oldOHR = window.onunhandledrejection + const oldOHR = window.onunhandledrejection window.onunhandledrejection = function (evt) { - if (typeof oldOHR === 'function') oldOHR.apply(this, arguments) + if (typeof oldOHR === 'function') { + oldOHR.apply(this, arguments) + } throw evt.detail.reason } } diff --git a/test/integration/index.js b/test/integration/index.js index b266ddf37..1c4523e1e 100644 --- a/test/integration/index.js +++ b/test/integration/index.js @@ -19,7 +19,9 @@ pump( b.bundle(), writeStream, (err) => { - if (err) throw err + if (err) { + throw err + } console.log(`Integration test build completed: "${bundlePath}"`) process.exit(0) } diff --git a/test/lib/mock-encryptor.js b/test/lib/mock-encryptor.js index 23ab2404f..0f70b5b3c 100644 --- a/test/lib/mock-encryptor.js +++ b/test/lib/mock-encryptor.js @@ -1,5 +1,5 @@ -var mockHex = '0xabcdef0123456789' -var mockKey = Buffer.alloc(32) +const mockHex = '0xabcdef0123456789' +const mockKey = Buffer.alloc(32) let cacheVal module.exports = { diff --git a/test/lib/mock-simple-keychain.js b/test/lib/mock-simple-keychain.js index d3addc3e8..74e6fd8a2 100644 --- a/test/lib/mock-simple-keychain.js +++ b/test/lib/mock-simple-keychain.js @@ -1,4 +1,4 @@ -var fakeWallet = { +const fakeWallet = { privKey: '0x123456788890abcdef', address: '0xfedcba0987654321', } @@ -6,7 +6,9 @@ const type = 'Simple Key Pair' module.exports = class MockSimpleKeychain { - static type () { return type } + static type () { + return type + } constructor (opts) { this.type = type @@ -26,7 +28,7 @@ module.exports = class MockSimpleKeychain { } addAccounts (n = 1) { - for (var i = 0; i < n; i++) { + for (let i = 0; i < n; i++) { this.wallets.push(fakeWallet) } } diff --git a/test/lib/react-trigger-change.js b/test/lib/react-trigger-change.js index d169dd614..52fe779c1 100644 --- a/test/lib/react-trigger-change.js +++ b/test/lib/react-trigger-change.js @@ -18,7 +18,7 @@ // Constants and functions are declared inside the closure. // In this way, reactTriggerChange can be passed directly to executeScript in Selenium. module.exports = function reactTriggerChange (node) { - var supportedInputTypes = { + const supportedInputTypes = { color: true, date: true, datetime: true, @@ -35,27 +35,27 @@ module.exports = function reactTriggerChange (node) { url: true, week: true, } - var nodeName = node.nodeName.toLowerCase() - var type = node.type - var event - var descriptor - var initialValue - var initialChecked - var initialCheckedRadio + const nodeName = node.nodeName.toLowerCase() + const type = node.type + let event + let descriptor + let initialValue + let initialChecked + let initialCheckedRadio // Do not try to delete non-configurable properties. // Value and checked properties on DOM elements are non-configurable in PhantomJS. function deletePropertySafe (elem, prop) { - var desc = Object.getOwnPropertyDescriptor(elem, prop) + const desc = Object.getOwnPropertyDescriptor(elem, prop) if (desc && desc.configurable) { delete elem[prop] } } function getCheckedRadio (radio) { - var name = radio.name - var radios - var i + const name = radio.name + let radios + let i if (name) { radios = document.querySelectorAll('input[type="radio"][name="' + name + '"]') for (i = 0; i < radios.length; i += 1) { diff --git a/test/lib/util.js b/test/lib/util.js index 4c5d789d1..be7e240a4 100644 --- a/test/lib/util.js +++ b/test/lib/util.js @@ -15,7 +15,9 @@ async function findAsync (container, selector, opts) { try { return await pollUntilTruthy(() => { const result = container.find(selector) - if (result.length > 0) return result + if (result.length > 0) { + return result + } }, opts) } catch (err) { throw new Error(`Failed to find element within interval: "${selector}"`) @@ -26,7 +28,9 @@ async function queryAsync (jQuery, selector, opts) { try { return await pollUntilTruthy(() => { const result = jQuery(selector) - if (result.length > 0) return result + if (result.length > 0) { + return result + } }, opts) } catch (err) { throw new Error(`Failed to find element within interval: "${selector}"`) diff --git a/test/setup.js b/test/setup.js index bccb4d58e..5aa6e59dd 100644 --- a/test/setup.js +++ b/test/setup.js @@ -5,6 +5,10 @@ require('@babel/register')({ require('./helper') window.SVGPathElement = window.SVGPathElement || { prototype: {} } -window.fetch = window.fetch || function fetch () { return Promise.resolve() } +window.fetch = window.fetch || function fetch () { + return Promise.resolve() +} global.indexedDB = {} -global.fetch = global.fetch || function fetch () { return Promise.resolve() } +global.fetch = global.fetch || function fetch () { + return Promise.resolve() +} diff --git a/test/unit/actions/config_test.js b/test/unit/actions/config_test.js index 9127474a8..39224a9b1 100644 --- a/test/unit/actions/config_test.js +++ b/test/unit/actions/config_test.js @@ -1,13 +1,13 @@ // var jsdom = require('mocha-jsdom') -var assert = require('assert') -var freeze = require('deep-freeze-strict') -var path = require('path') +const assert = require('assert') +const freeze = require('deep-freeze-strict') +const path = require('path') -var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'store', 'actions.js')) -var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'ducks', 'index.js')) +const actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'store', 'actions.js')) +const reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'ducks', 'index.js')) describe('config view actions', function () { - var initialState = { + const initialState = { metamask: { rpcTarget: 'foo', frequentRpcList: [], @@ -22,7 +22,7 @@ describe('config view actions', function () { describe('SHOW_CONFIG_PAGE', function () { it('should set appState.currentView.name to config', function () { - var result = reducers(initialState, actions.showConfigPage()) + const result = reducers(initialState, actions.showConfigPage()) assert.equal(result.appState.currentView.name, 'config') }) }) @@ -34,7 +34,7 @@ describe('config view actions', function () { value: 'foo', } - var result = reducers(initialState, action) + const result = reducers(initialState, action) assert.equal(result.metamask.provider.type, 'rpc') assert.equal(result.metamask.provider.rpcTarget, 'foo') }) diff --git a/test/unit/actions/set_selected_account_test.js b/test/unit/actions/set_selected_account_test.js index 36d312d7b..0488c844e 100644 --- a/test/unit/actions/set_selected_account_test.js +++ b/test/unit/actions/set_selected_account_test.js @@ -1,14 +1,14 @@ // var jsdom = require('mocha-jsdom') -var assert = require('assert') -var freeze = require('deep-freeze-strict') -var path = require('path') +const assert = require('assert') +const freeze = require('deep-freeze-strict') +const path = require('path') -var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'store', 'actions.js')) -var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'ducks', 'index.js')) +const actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'store', 'actions.js')) +const reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'ducks', 'index.js')) describe('SET_SELECTED_ACCOUNT', function () { it('sets the state.appState.activeAddress property of the state to the action.value', function () { - var initialState = { + const initialState = { appState: { activeAddress: 'foo', }, @@ -21,14 +21,14 @@ describe('SET_SELECTED_ACCOUNT', function () { } freeze(action) - var resultingState = reducers(initialState, action) + const resultingState = reducers(initialState, action) assert.equal(resultingState.appState.activeAddress, action.value) }) }) describe('SHOW_ACCOUNT_DETAIL', function () { it('updates metamask state', function () { - var initialState = { + const initialState = { metamask: { selectedAddress: 'foo', }, @@ -41,7 +41,7 @@ describe('SHOW_ACCOUNT_DETAIL', function () { } freeze(action) - var resultingState = reducers(initialState, action) + const resultingState = reducers(initialState, action) assert.equal(resultingState.metamask.selectedAddress, action.value) }) }) diff --git a/test/unit/actions/tx_test.js b/test/unit/actions/tx_test.js index 66378b594..0b2da16aa 100644 --- a/test/unit/actions/tx_test.js +++ b/test/unit/actions/tx_test.js @@ -1,5 +1,5 @@ -var assert = require('assert') -var path = require('path') +const assert = require('assert') +const path = require('path') import configureMockStore from 'redux-mock-store' import thunk from 'redux-thunk' @@ -33,9 +33,15 @@ describe('tx confirmation screen', function () { describe('cancelTx', function () { before(function (done) { actions._setBackgroundConnection({ - approveTransaction (_, cb) { cb('An error!') }, - cancelTransaction (_, cb) { cb() }, - getState (cb) { cb() }, + approveTransaction (_, cb) { + cb('An error!') + }, + cancelTransaction (_, cb) { + cb() + }, + getState (cb) { + cb() + }, }) done() }) diff --git a/test/unit/actions/view_info_test.js b/test/unit/actions/view_info_test.js index 5785a368c..4792af727 100644 --- a/test/unit/actions/view_info_test.js +++ b/test/unit/actions/view_info_test.js @@ -1,14 +1,14 @@ // var jsdom = require('mocha-jsdom') -var assert = require('assert') -var freeze = require('deep-freeze-strict') -var path = require('path') +const assert = require('assert') +const freeze = require('deep-freeze-strict') +const path = require('path') -var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'store', 'actions.js')) -var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'ducks', 'index.js')) +const actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'store', 'actions.js')) +const reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'ducks', 'index.js')) describe('SHOW_INFO_PAGE', function () { it('sets the state.appState.currentView.name property to info', function () { - var initialState = { + const initialState = { appState: { activeAddress: 'foo', }, @@ -16,7 +16,7 @@ describe('SHOW_INFO_PAGE', function () { freeze(initialState) const action = actions.showInfoPage() - var resultingState = reducers(initialState, action) + const resultingState = reducers(initialState, action) assert.equal(resultingState.appState.currentView.name, 'info') }) }) diff --git a/test/unit/actions/warning_test.js b/test/unit/actions/warning_test.js index e57374cda..ac639f600 100644 --- a/test/unit/actions/warning_test.js +++ b/test/unit/actions/warning_test.js @@ -1,14 +1,14 @@ // var jsdom = require('mocha-jsdom') -var assert = require('assert') -var freeze = require('deep-freeze-strict') -var path = require('path') +const assert = require('assert') +const freeze = require('deep-freeze-strict') +const path = require('path') -var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'store', 'actions.js')) -var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'ducks', 'index.js')) +const actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'store', 'actions.js')) +const reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'ducks', 'index.js')) describe('action DISPLAY_WARNING', function () { it('sets appState.warning to provided value', function () { - var initialState = { + const initialState = { appState: {}, } freeze(initialState) diff --git a/test/unit/app/buy-eth-url.spec.js b/test/unit/app/buy-eth-url.spec.js index 3cdad93b5..1a5b1161c 100644 --- a/test/unit/app/buy-eth-url.spec.js +++ b/test/unit/app/buy-eth-url.spec.js @@ -17,7 +17,7 @@ describe('buy-eth-url', function () { network: '42', } - it('returns coinbase url with amount and address for network 1', function () { + it('returns wyre url with address for network 1', function () { const wyreUrl = getBuyEthUrl(mainnet) assert.equal(wyreUrl, 'https://pay.sendwyre.com/?dest=ethereum:0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc&destCurrency=ETH&accountId=AC-7AG3W4XH4N2&paymentMethod=debit-card') diff --git a/test/unit/app/controllers/detect-tokens-test.js b/test/unit/app/controllers/detect-tokens-test.js index 8f18406f4..c65ee97ab 100644 --- a/test/unit/app/controllers/detect-tokens-test.js +++ b/test/unit/app/controllers/detect-tokens-test.js @@ -54,7 +54,7 @@ describe('DetectTokensController', () => { controller.isOpen = true controller.isUnlocked = true - var stub = sandbox.stub(controller, 'detectNewTokens') + const stub = sandbox.stub(controller, 'detectNewTokens') clock.tick(1) sandbox.assert.notCalled(stub) @@ -70,7 +70,7 @@ describe('DetectTokensController', () => { controller.isOpen = true controller.isUnlocked = true - var stub = sandbox.stub(controller, 'detectTokenBalance') + const stub = sandbox.stub(controller, 'detectTokenBalance') .withArgs('0x0D262e5dC4A06a0F1c90cE79C7a60C09DfC884E4').returns(true) .withArgs('0xBC86727E770de68B1060C91f6BB6945c73e10388').returns(true) @@ -114,7 +114,7 @@ describe('DetectTokensController', () => { it('should trigger detect new tokens when change address', async () => { controller.isOpen = true controller.isUnlocked = true - var stub = sandbox.stub(controller, 'detectNewTokens') + const stub = sandbox.stub(controller, 'detectNewTokens') await preferences.setSelectedAddress('0xbc86727e770de68b1060c91f6bb6945c73e10388') sandbox.assert.called(stub) }) @@ -122,7 +122,7 @@ describe('DetectTokensController', () => { it('should trigger detect new tokens when submit password', async () => { controller.isOpen = true controller.selectedAddress = '0x0' - var stub = sandbox.stub(controller, 'detectNewTokens') + const stub = sandbox.stub(controller, 'detectNewTokens') await controller._keyringMemStore.updateState({ isUnlocked: true }) sandbox.assert.called(stub) }) @@ -130,7 +130,7 @@ describe('DetectTokensController', () => { it('should not trigger detect new tokens when not open or not unlocked', async () => { controller.isOpen = true controller.isUnlocked = false - var stub = sandbox.stub(controller, 'detectTokenBalance') + const stub = sandbox.stub(controller, 'detectTokenBalance') clock.tick(180000) sandbox.assert.notCalled(stub) controller.isOpen = false diff --git a/test/unit/app/controllers/metamask-controller-test.js b/test/unit/app/controllers/metamask-controller-test.js index 7eac91a9b..a904dec08 100644 --- a/test/unit/app/controllers/metamask-controller-test.js +++ b/test/unit/app/controllers/metamask-controller-test.js @@ -227,7 +227,9 @@ describe('MetaMaskController', function () { it('should be able to call newVaultAndRestore despite a mistake.', async function () { const password = 'what-what-what' sandbox.stub(metamaskController, 'getBalance') - metamaskController.getBalance.callsFake(() => { return Promise.resolve('0x0') }) + metamaskController.getBalance.callsFake(() => { + return Promise.resolve('0x0') + }) await metamaskController.createNewVaultAndRestore(password, TEST_SEED.slice(0, -1)).catch(() => null) await metamaskController.createNewVaultAndRestore(password, TEST_SEED) @@ -237,7 +239,9 @@ describe('MetaMaskController', function () { it('should clear previous identities after vault restoration', async () => { sandbox.stub(metamaskController, 'getBalance') - metamaskController.getBalance.callsFake(() => { return Promise.resolve('0x0') }) + metamaskController.getBalance.callsFake(() => { + return Promise.resolve('0x0') + }) await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED) assert.deepEqual(metamaskController.getState().identities, { @@ -676,7 +680,9 @@ describe('MetaMaskController', function () { beforeEach(async () => { sandbox.stub(metamaskController, 'getBalance') - metamaskController.getBalance.callsFake(() => { return Promise.resolve('0x0') }) + metamaskController.getBalance.callsFake(() => { + return Promise.resolve('0x0') + }) await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED_ALT) @@ -734,7 +740,9 @@ describe('MetaMaskController', function () { beforeEach(async function () { sandbox.stub(metamaskController, 'getBalance') - metamaskController.getBalance.callsFake(() => { return Promise.resolve('0x0') }) + metamaskController.getBalance.callsFake(() => { + return Promise.resolve('0x0') + }) await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED_ALT) @@ -809,7 +817,9 @@ describe('MetaMaskController', function () { const { promise, resolve } = deferredPromise() streamTest = createThoughStream((chunk, _, cb) => { - if (chunk.name !== 'phishing') return cb() + if (chunk.name !== 'phishing') { + return cb() + } assert.equal(chunk.data.hostname, phishingUrl.hostname) resolve() cb() @@ -929,6 +939,8 @@ describe('MetaMaskController', function () { function deferredPromise () { let resolve - const promise = new Promise(_resolve => { resolve = _resolve }) + const promise = new Promise(_resolve => { + resolve = _resolve + }) return { promise, resolve } } diff --git a/test/unit/app/controllers/network/pending-middleware-test.js b/test/unit/app/controllers/network/pending-middleware-test.js index 838395b0b..ac6f8ad9a 100644 --- a/test/unit/app/controllers/network/pending-middleware-test.js +++ b/test/unit/app/controllers/network/pending-middleware-test.js @@ -19,7 +19,9 @@ describe('#createPendingNonceMiddleware', function () { it('should fill the result with a the "pending" nonce', (done) => { const req = { method: 'eth_getTransactionCount', params: [address, 'pending'] } const res = {} - pendingNonceMiddleware(req, res, () => { done(new Error('should not have called next')) }, () => { + pendingNonceMiddleware(req, res, () => { + done(new Error('should not have called next')) + }, () => { assert(res.result === '0x2') done() }) @@ -63,7 +65,9 @@ describe('#createPendingTxMiddleware', function () { returnUndefined = false const req = { method: 'eth_getTransactionByHash', params: [address, 'pending'] } const res = {} - pendingTxMiddleware(req, res, () => { done(new Error('should not have called next')) }, () => { + pendingTxMiddleware(req, res, () => { + done(new Error('should not have called next')) + }, () => { /* // uncomment this section for debugging help with non matching keys const coppy = {...res.result} diff --git a/test/unit/app/controllers/preferences-controller-test.js b/test/unit/app/controllers/preferences-controller-test.js index 7e047a2a8..7aea39a36 100644 --- a/test/unit/app/controllers/preferences-controller-test.js +++ b/test/unit/app/controllers/preferences-controller-test.js @@ -1,6 +1,7 @@ const assert = require('assert') const ObservableStore = require('obs-store') const PreferencesController = require('../../../../app/scripts/controllers/preferences') +const { addInternalMethodPrefix } = require('../../../../app/scripts/controllers/permissions') const sinon = require('sinon') describe('preferences controller', function () { @@ -342,7 +343,7 @@ describe('preferences controller', function () { }) describe('on watchAsset', function () { - var stubNext, stubEnd, stubHandleWatchAssetERC20, asy, req, res + let stubNext, stubEnd, stubHandleWatchAssetERC20, asy, req, res const sandbox = sinon.createSandbox() beforeEach(() => { @@ -359,8 +360,8 @@ describe('preferences controller', function () { it('shouldn not do anything if method not corresponds', async function () { const asy = {next: () => {}, end: () => {}} - var stubNext = sandbox.stub(asy, 'next') - var stubEnd = sandbox.stub(asy, 'end').returns(0) + const stubNext = sandbox.stub(asy, 'next') + const stubEnd = sandbox.stub(asy, 'end').returns(0) req.method = 'metamask' await preferencesController.requestWatchAsset(req, res, asy.next, asy.end) sandbox.assert.notCalled(stubEnd) @@ -368,14 +369,14 @@ describe('preferences controller', function () { }) it('should do something if method is supported', async function () { const asy = {next: () => {}, end: () => {}} - var stubNext = sandbox.stub(asy, 'next') - var stubEnd = sandbox.stub(asy, 'end').returns(0) + const stubNext = sandbox.stub(asy, 'next') + const stubEnd = sandbox.stub(asy, 'end').returns(0) req.method = 'metamask_watchAsset' req.params.type = 'someasset' await preferencesController.requestWatchAsset(req, res, asy.next, asy.end) sandbox.assert.called(stubEnd) sandbox.assert.notCalled(stubNext) - req.method = 'wallet_watchAsset' + req.method = addInternalMethodPrefix('watchAsset') req.params.type = 'someasset' await preferencesController.requestWatchAsset(req, res, asy.next, asy.end) sandbox.assert.calledTwice(stubEnd) @@ -400,7 +401,7 @@ describe('preferences controller', function () { }) describe('on watchAsset of type ERC20', function () { - var req + let req const sandbox = sinon.createSandbox() beforeEach(() => { @@ -455,28 +456,44 @@ describe('preferences controller', function () { }) it('should validate ERC20 asset correctly', async function () { const validateSpy = sandbox.spy(preferencesController._validateERC20AssetParams) - try { validateSpy({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABC', decimals: 0}) } catch (e) {} + try { + validateSpy({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABC', decimals: 0}) + } catch (e) {} assert.equal(validateSpy.threw(), false, 'correct options object') const validateSpyAddress = sandbox.spy(preferencesController._validateERC20AssetParams) - try { validateSpyAddress({symbol: 'ABC', decimals: 0}) } catch (e) {} + try { + validateSpyAddress({symbol: 'ABC', decimals: 0}) + } catch (e) {} assert.equal(validateSpyAddress.threw(), true, 'options object with no address') const validateSpySymbol = sandbox.spy(preferencesController._validateERC20AssetParams) - try { validateSpySymbol({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', decimals: 0}) } catch (e) {} + try { + validateSpySymbol({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', decimals: 0}) + } catch (e) {} assert.equal(validateSpySymbol.threw(), true, 'options object with no symbol') const validateSpyDecimals = sandbox.spy(preferencesController._validateERC20AssetParams) - try { validateSpyDecimals({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABC'}) } catch (e) {} + try { + validateSpyDecimals({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABC'}) + } catch (e) {} assert.equal(validateSpyDecimals.threw(), true, 'options object with no decimals') const validateSpyInvalidSymbol = sandbox.spy(preferencesController._validateERC20AssetParams) - try { validateSpyInvalidSymbol({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABCDEFGHI', decimals: 0}) } catch (e) {} + try { + validateSpyInvalidSymbol({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABCDEFGHI', decimals: 0}) + } catch (e) {} assert.equal(validateSpyInvalidSymbol.threw(), true, 'options object with invalid symbol') const validateSpyInvalidDecimals1 = sandbox.spy(preferencesController._validateERC20AssetParams) - try { validateSpyInvalidDecimals1({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABCDEFGHI', decimals: -1}) } catch (e) {} + try { + validateSpyInvalidDecimals1({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABCDEFGHI', decimals: -1}) + } catch (e) {} assert.equal(validateSpyInvalidDecimals1.threw(), true, 'options object with decimals less than zero') const validateSpyInvalidDecimals2 = sandbox.spy(preferencesController._validateERC20AssetParams) - try { validateSpyInvalidDecimals2({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABCDEFGHI', decimals: 38}) } catch (e) {} + try { + validateSpyInvalidDecimals2({rawAddress: '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07', symbol: 'ABCDEFGHI', decimals: 38}) + } catch (e) {} assert.equal(validateSpyInvalidDecimals2.threw(), true, 'options object with decimals more than 36') const validateSpyInvalidAddress = sandbox.spy(preferencesController._validateERC20AssetParams) - try { validateSpyInvalidAddress({rawAddress: '0x123', symbol: 'ABC', decimals: 0}) } catch (e) {} + try { + validateSpyInvalidAddress({rawAddress: '0x123', symbol: 'ABC', decimals: 0}) + } catch (e) {} assert.equal(validateSpyInvalidAddress.threw(), true, 'options object with address invalid') }) }) diff --git a/test/unit/app/controllers/provider-approval-test.js b/test/unit/app/controllers/provider-approval-test.js deleted file mode 100644 index eeb9e813b..000000000 --- a/test/unit/app/controllers/provider-approval-test.js +++ /dev/null @@ -1,330 +0,0 @@ -const assert = require('assert') -const sinon = require('sinon') -const ProviderApprovalController = require('../../../../app/scripts/controllers/provider-approval') - -const mockLockedKeyringController = { - memStore: { - getState: () => ({ - isUnlocked: false, - }), - }, -} - -const mockUnlockedKeyringController = { - memStore: { - getState: () => ({ - isUnlocked: true, - }), - }, -} - -describe('ProviderApprovalController', () => { - describe('#_handleProviderRequest', () => { - it('should add a pending provider request when unlocked', () => { - const controller = new ProviderApprovalController({ - keyringController: mockUnlockedKeyringController, - }) - - const metadata = { - hostname: 'https://example.com', - origin: 'example.com', - siteTitle: 'Example', - siteImage: 'https://example.com/logo.svg', - } - - controller._handleProviderRequest(metadata) - assert.deepEqual(controller._getMergedState(), { - approvedOrigins: {}, - providerRequests: [metadata], - }) - }) - - it('should add a pending provider request when locked', () => { - const controller = new ProviderApprovalController({ - keyringController: mockLockedKeyringController, - }) - - const metadata = { - hostname: 'https://example.com', - origin: 'example.com', - siteTitle: 'Example', - siteImage: 'https://example.com/logo.svg', - } - controller._handleProviderRequest(metadata) - assert.deepEqual(controller._getMergedState(), { - approvedOrigins: {}, - providerRequests: [metadata], - }) - }) - - it('should add a 2nd pending provider request when unlocked', () => { - const controller = new ProviderApprovalController({ - keyringController: mockUnlockedKeyringController, - }) - - const metadata = [{ - hostname: 'https://example1.com', - origin: 'example1.com', - siteTitle: 'Example 1', - siteImage: 'https://example1.com/logo.svg', - }, { - hostname: 'https://example2.com', - origin: 'example2.com', - siteTitle: 'Example 2', - siteImage: 'https://example2.com/logo.svg', - }] - - controller._handleProviderRequest(metadata[0]) - controller._handleProviderRequest(metadata[1]) - assert.deepEqual(controller._getMergedState(), { - approvedOrigins: {}, - providerRequests: metadata, - }) - }) - - it('should add a 2nd pending provider request when locked', () => { - const controller = new ProviderApprovalController({ - keyringController: mockLockedKeyringController, - }) - - const metadata = [{ - hostname: 'https://example1.com', - origin: 'example1.com', - siteTitle: 'Example 1', - siteImage: 'https://example1.com/logo.svg', - }, { - hostname: 'https://example2.com', - origin: 'example2.com', - siteTitle: 'Example 2', - siteImage: 'https://example2.com/logo.svg', - }] - - controller._handleProviderRequest(metadata[0]) - controller._handleProviderRequest(metadata[1]) - assert.deepEqual(controller._getMergedState(), { - approvedOrigins: {}, - providerRequests: metadata, - }) - }) - - it('should call openPopup when unlocked and when given', () => { - const openPopup = sinon.spy() - const controller = new ProviderApprovalController({ - openPopup, - keyringController: mockUnlockedKeyringController, - }) - - const metadata = { - hostname: 'https://example.com', - origin: 'example.com', - siteTitle: 'Example', - siteImage: 'https://example.com/logo.svg', - } - controller._handleProviderRequest(metadata) - assert.ok(openPopup.calledOnce) - }) - - it('should call openPopup when locked and when given', () => { - const openPopup = sinon.spy() - const controller = new ProviderApprovalController({ - openPopup, - keyringController: mockLockedKeyringController, - }) - - const metadata = { - hostname: 'https://example.com', - origin: 'example.com', - siteTitle: 'Example', - siteImage: 'https://example.com/logo.svg', - } - controller._handleProviderRequest(metadata) - assert.ok(openPopup.calledOnce) - }) - - it('should NOT call openPopup when unlocked and when the domain has already been approved', () => { - const openPopup = sinon.spy() - const controller = new ProviderApprovalController({ - openPopup, - keyringController: mockUnlockedKeyringController, - }) - - controller.store.updateState({ - approvedOrigins: { - 'example.com': { - siteTitle: 'Example', - siteImage: 'https://example.com/logo.svg', - }, - }, - }) - const metadata = { - hostname: 'https://example.com', - origin: 'example.com', - siteTitle: 'Example', - siteImage: 'https://example.com/logo.svg', - } - controller._handleProviderRequest(metadata) - assert.ok(openPopup.notCalled) - }) - }) - - describe('#approveProviderRequestByOrigin', () => { - it('should mark the origin as approved and remove the provider request', () => { - const controller = new ProviderApprovalController({ - keyringController: mockUnlockedKeyringController, - }) - - const metadata = { - hostname: 'https://example.com', - origin: 'example.com', - siteTitle: 'Example', - siteImage: 'https://example.com/logo.svg', - } - controller._handleProviderRequest(metadata) - controller.approveProviderRequestByOrigin('example.com') - assert.deepEqual(controller._getMergedState(), { - providerRequests: [], - approvedOrigins: { - 'example.com': { - hostname: 'https://example.com', - siteTitle: 'Example', - siteImage: 'https://example.com/logo.svg', - }, - }, - }) - }) - - it('should mark the origin as approved and multiple requests for the same domain', () => { - const controller = new ProviderApprovalController({ - keyringController: mockUnlockedKeyringController, - }) - - const metadata = { - hostname: 'https://example.com', - origin: 'example.com', - siteTitle: 'Example', - siteImage: 'https://example.com/logo.svg', - } - controller._handleProviderRequest(metadata) - controller._handleProviderRequest(metadata) - controller.approveProviderRequestByOrigin('example.com') - assert.deepEqual(controller._getMergedState(), { - providerRequests: [], - approvedOrigins: { - 'example.com': { - hostname: 'https://example.com', - siteTitle: 'Example', - siteImage: 'https://example.com/logo.svg', - }, - }, - }) - }) - - it('should mark the origin as approved without a provider request', () => { - const controller = new ProviderApprovalController({ - keyringController: mockUnlockedKeyringController, - }) - - controller.approveProviderRequestByOrigin('example.com') - assert.deepEqual(controller._getMergedState(), { - providerRequests: [], - approvedOrigins: { - 'example.com': { - hostname: null, - siteTitle: null, - siteImage: null, - }, - }, - }) - }) - }) - - describe('#rejectProviderRequestByOrigin', () => { - it('should remove the origin from approved', () => { - const controller = new ProviderApprovalController({ - keyringController: mockUnlockedKeyringController, - }) - - const metadata = { - hostname: 'https://example.com', - origin: 'example.com', - siteTitle: 'Example', - siteImage: 'https://example.com/logo.svg', - } - controller._handleProviderRequest(metadata) - controller.approveProviderRequestByOrigin('example.com') - controller.rejectProviderRequestByOrigin('example.com') - assert.deepEqual(controller._getMergedState(), { - providerRequests: [], - approvedOrigins: {}, - }) - }) - - it('should reject the origin even without a pending request', () => { - const controller = new ProviderApprovalController({ - keyringController: mockUnlockedKeyringController, - }) - - controller.rejectProviderRequestByOrigin('example.com') - assert.deepEqual(controller._getMergedState(), { - providerRequests: [], - approvedOrigins: {}, - }) - }) - }) - - describe('#clearApprovedOrigins', () => { - it('should clear the approved origins', () => { - const controller = new ProviderApprovalController({ - keyringController: mockUnlockedKeyringController, - }) - - const metadata = { - hostname: 'https://example.com', - origin: 'example.com', - siteTitle: 'Example', - siteImage: 'https://example.com/logo.svg', - } - controller._handleProviderRequest(metadata) - controller.approveProviderRequestByOrigin('example.com') - controller.clearApprovedOrigins() - assert.deepEqual(controller._getMergedState(), { - providerRequests: [], - approvedOrigins: {}, - }) - }) - }) - - describe('#shouldExposeAccounts', () => { - it('should return true for an approved origin', () => { - const controller = new ProviderApprovalController({ - keyringController: mockUnlockedKeyringController, - }) - - const metadata = { - hostname: 'https://example.com', - origin: 'example.com', - siteTitle: 'Example', - siteImage: 'https://example.com/logo.svg', - } - controller._handleProviderRequest(metadata) - controller.approveProviderRequestByOrigin('example.com') - assert.ok(controller.shouldExposeAccounts('example.com')) - }) - - it('should return false for an origin not yet approved', () => { - const controller = new ProviderApprovalController({ - keyringController: mockUnlockedKeyringController, - }) - - const metadata = { - hostname: 'https://example.com', - origin: 'example.com', - siteTitle: 'Example', - siteImage: 'https://example.com/logo.svg', - } - controller._handleProviderRequest(metadata) - controller.approveProviderRequestByOrigin('example.com') - assert.ok(!controller.shouldExposeAccounts('bad.website')) - }) - }) -}) diff --git a/test/unit/app/controllers/transactions/pending-tx-test.js b/test/unit/app/controllers/transactions/pending-tx-test.js index e1de5731b..622a53b18 100644 --- a/test/unit/app/controllers/transactions/pending-tx-test.js +++ b/test/unit/app/controllers/transactions/pending-tx-test.js @@ -40,13 +40,19 @@ describe('PendingTransactionTracker', function () { return { releaseLock: () => {} } }, }, - getPendingTransactions: () => { return [] }, - getCompletedTransactions: () => { return [] }, + getPendingTransactions: () => { + return [] + }, + getCompletedTransactions: () => { + return [] + }, publishTransaction: () => {}, confirmTransaction: () => {}, }) - pendingTxTracker._getBlock = (blockNumber) => { return {number: blockNumber, transactions: []} } + pendingTxTracker._getBlock = (blockNumber) => { + return {number: blockNumber, transactions: []} + } }) describe('_checkPendingTx state management', function () { @@ -150,14 +156,18 @@ describe('PendingTransactionTracker', function () { txMeta2.id = 2 txMeta3.id = 3 txList = [txMeta, txMeta2, txMeta3].map((tx) => { - tx.processed = new Promise((resolve) => { tx.resolve = resolve }) + tx.processed = new Promise((resolve) => { + tx.resolve = resolve + }) return tx }) }) it('should warp all txMeta\'s in #updatePendingTxs', function (done) { pendingTxTracker.getPendingTransactions = () => txList - pendingTxTracker._checkPendingTx = (tx) => { tx.resolve(tx) } + pendingTxTracker._checkPendingTx = (tx) => { + tx.resolve(tx) + } Promise.all(txList.map((tx) => tx.processed)) .then(() => done()) .catch(done) @@ -171,7 +181,9 @@ describe('PendingTransactionTracker', function () { beforeEach(function () { const txMeta2 = txMeta3 = txMeta txList = [txMeta, txMeta2, txMeta3].map((tx) => { - tx.processed = new Promise((resolve) => { tx.resolve = resolve }) + tx.processed = new Promise((resolve) => { + tx.resolve = resolve + }) return tx }) }) @@ -181,7 +193,9 @@ describe('PendingTransactionTracker', function () { }) it('should call #_resubmitTx for all pending tx\'s', function (done) { pendingTxTracker.getPendingTransactions = () => txList - pendingTxTracker._resubmitTx = async (tx) => { tx.resolve(tx) } + pendingTxTracker._resubmitTx = async (tx) => { + tx.resolve(tx) + } Promise.all(txList.map((tx) => tx.processed)) .then(() => done()) .catch(done) @@ -225,7 +239,9 @@ describe('PendingTransactionTracker', function () { }) pendingTxTracker.getPendingTransactions = () => txList - pendingTxTracker._resubmitTx = async () => { 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(() => done()) .catch(done) @@ -369,7 +385,9 @@ describe('PendingTransactionTracker', function () { rawTx: '0xf86c808504a817c800827b0d940c62bb85faa3311a998d3aba8098c1235c564966880de0b6b3a7640000802aa08ff665feb887a25d4099e40e11f0fef93ee9608f404bd3f853dd9e84ed3317a6a02ec9d3d1d6e176d4d2593dd760e74ccac753e6a0ea0d00cc9789d0d7ff1f471d', }] pendingTxTracker.getCompletedTransactions = (address) => { - if (!address) throw new Error('unless behavior has changed #_checkIfNonceIsTaken needs a filtered list of transactions to see if the nonce is taken') + if (!address) { + throw new Error('unless behavior has changed #_checkIfNonceIsTaken needs a filtered list of transactions to see if the nonce is taken') + } return confirmedTxList } }) diff --git a/test/unit/app/controllers/transactions/tx-controller-test.js b/test/unit/app/controllers/transactions/tx-controller-test.js index 76b8e5025..d398c7e04 100644 --- a/test/unit/app/controllers/transactions/tx-controller-test.js +++ b/test/unit/app/controllers/transactions/tx-controller-test.js @@ -38,7 +38,9 @@ describe('Transaction Controller', function () { blockTrackerStub.getLatestBlock = noop txController = new TransactionController({ provider, - getGasPrice: function () { return '0xee6b2800' }, + getGasPrice: function () { + return '0xee6b2800' + }, networkStore: netStore, txHistoryLimit: 10, blockTracker: blockTrackerStub, @@ -46,6 +48,7 @@ describe('Transaction Controller', function () { ethTx.sign(fromAccount.key) resolve() }), + getPermittedAccounts: () => {}, }) txController.nonceTracker.getNonceLock = () => Promise.resolve({ nextNonce: 0, releaseLock: noop }) }) @@ -162,8 +165,11 @@ describe('Transaction Controller', function () { txController.newUnapprovedTransaction(txParams) .catch((err) => { - if (err.message === 'MetaMask Tx Signature: User denied transaction signature.') done() - else done(err) + if (err.message === 'MetaMask Tx Signature: User denied transaction signature.') { + done() + } else { + done(err) + } }) }) }) @@ -171,13 +177,15 @@ describe('Transaction Controller', function () { describe('#addUnapprovedTransaction', function () { const selectedAddress = '0x1678a085c290ebd122dc42cba69373b5953b831d' - let getSelectedAddress + let getSelectedAddress, getPermittedAccounts beforeEach(function () { getSelectedAddress = sinon.stub(txController, 'getSelectedAddress').returns(selectedAddress) + getPermittedAccounts = sinon.stub(txController, 'getPermittedAccounts').returns([selectedAddress]) }) afterEach(function () { getSelectedAddress.restore() + getPermittedAccounts.restore() }) it('should add an unapproved transaction and return a valid txMeta', function (done) { @@ -209,8 +217,11 @@ describe('Transaction Controller', function () { txController.networkStore = new ObservableStore(1) txController.addUnapprovedTransaction({ from: selectedAddress, to: '0x0d1d4e623D10F9FBA5Db95830F7d3839406C6AF2' }) .catch((err) => { - if (err.message === 'Recipient is a public account') done() - else done(err) + if (err.message === 'Recipient is a public account') { + done() + } else { + done(err) + } }) }) @@ -239,8 +250,11 @@ describe('Transaction Controller', function () { txController.networkStore = new ObservableStore('loading') txController.addUnapprovedTransaction({ from: selectedAddress, to: '0x0d1d4e623D10F9FBA5Db95830F7d3839406C6AF2' }) .catch((err) => { - if (err.message === 'MetaMask is having trouble connecting to the network') done() - else done(err) + if (err.message === 'MetaMask is having trouble connecting to the network') { + done() + } else { + done(err) + } }) }) }) @@ -403,7 +417,9 @@ describe('Transaction Controller', function () { assert.equal(status, 'rejected', 'status should e rejected') assert.equal(txId, 0, 'id should e 0') done() - } catch (e) { done(e) } + } catch (e) { + done(e) + } }) txController.cancelTransaction(0) @@ -498,7 +514,9 @@ describe('Transaction Controller', function () { }) it('should ignore the error "Transaction Failed: known transaction" and be as usual', async function () { - providerResultStub['eth_sendRawTransaction'] = async (_, __, ___, end) => { end('Transaction Failed: known transaction') } + providerResultStub['eth_sendRawTransaction'] = async (_, __, ___, end) => { + end('Transaction Failed: known transaction') + } const rawTx = '0xf86204831e848082520894f231d46dd78806e1dd93442cf33c7671f853874880802ca05f973e540f2d3c2f06d3725a626b75247593cb36477187ae07ecfe0a4db3cf57a00259b52ee8c58baaa385fb05c3f96116e58de89bcc165cb3bfdfc708672fed8a' txController.txStateManager.addTx(txMeta) await txController.publishTransaction(txMeta.id, rawTx) @@ -617,7 +635,9 @@ describe('Transaction Controller', function () { _blockTrackerStub.getLatestBlock = noop const _txController = new TransactionController({ provider: _provider, - getGasPrice: function () { return '0xee6b2800' }, + getGasPrice: function () { + return '0xee6b2800' + }, networkStore: new ObservableStore(currentNetworkId), txHistoryLimit: 10, blockTracker: _blockTrackerStub, @@ -647,7 +667,9 @@ describe('Transaction Controller', function () { _blockTrackerStub.getLatestBlock = noop const _txController = new TransactionController({ provider: _provider, - getGasPrice: function () { return '0xee6b2800' }, + getGasPrice: function () { + return '0xee6b2800' + }, networkStore: new ObservableStore(currentNetworkId), txHistoryLimit: 10, blockTracker: _blockTrackerStub, 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 328c2ac6f..6136de66e 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 @@ -106,7 +106,9 @@ describe('Transaction state history helper', function () { assert.equal(result[0].path, expectedEntry1.path) assert.equal(result[0].value, expectedEntry1.value) assert.equal(result[0].value, expectedEntry1.value) - if (note) { assert.equal(result[0].note, note) } + if (note) { + assert.equal(result[0].note, note) + } assert.ok(result[0].timestamp >= before && result[0].timestamp <= after) diff --git a/test/unit/app/controllers/transactions/tx-utils-test.js b/test/unit/app/controllers/transactions/tx-utils-test.js index 65c8d35b0..e8bc8a5b7 100644 --- a/test/unit/app/controllers/transactions/tx-utils-test.js +++ b/test/unit/app/controllers/transactions/tx-utils-test.js @@ -5,7 +5,7 @@ const txUtils = require('../../../../../app/scripts/controllers/transactions/lib describe('txUtils', function () { describe('#validateTxParams', function () { it('does not throw for positive values', function () { - var sample = { + const sample = { from: '0x1678a085c290ebd122dc42cba69373b5953b831d', value: '0x01', } @@ -13,7 +13,7 @@ describe('txUtils', function () { }) it('returns error for negative values', function () { - var sample = { + const sample = { from: '0x1678a085c290ebd122dc42cba69373b5953b831d', value: '-0x01', } @@ -66,7 +66,9 @@ describe('txUtils', function () { from: '0x1678a085c290ebd122dc42cba69373b5953b831d', to: '0x', } - assert.throws(() => { txUtils.validateRecipient(zeroRecipientTxParams) }, Error, 'Invalid recipient address') + assert.throws(() => { + txUtils.validateRecipient(zeroRecipientTxParams) + }, Error, 'Invalid recipient address') }) }) @@ -76,19 +78,27 @@ describe('txUtils', function () { // where from is undefined const txParams = {} - assert.throws(() => { txUtils.validateFrom(txParams) }, Error, `Invalid from address ${txParams.from} not a string`) + assert.throws(() => { + txUtils.validateFrom(txParams) + }, Error, `Invalid from address ${txParams.from} not a string`) // where from is array txParams.from = [] - assert.throws(() => { txUtils.validateFrom(txParams) }, Error, `Invalid from address ${txParams.from} not a string`) + assert.throws(() => { + txUtils.validateFrom(txParams) + }, Error, `Invalid from address ${txParams.from} not a string`) // where from is a object txParams.from = {} - assert.throws(() => { txUtils.validateFrom(txParams) }, Error, `Invalid from address ${txParams.from} not a string`) + assert.throws(() => { + txUtils.validateFrom(txParams) + }, Error, `Invalid from address ${txParams.from} not a string`) // where from is a invalid address txParams.from = 'im going to fail' - assert.throws(() => { txUtils.validateFrom(txParams) }, Error, `Invalid from address`) + assert.throws(() => { + txUtils.validateFrom(txParams) + }, Error, `Invalid from address`) // should run txParams.from = '0x1678a085c290ebd122dc42cba69373b5953b831d' diff --git a/test/unit/app/edge-encryptor-test.js b/test/unit/app/edge-encryptor-test.js index ad873e351..3f2e8830a 100644 --- a/test/unit/app/edge-encryptor-test.js +++ b/test/unit/app/edge-encryptor-test.js @@ -2,8 +2,8 @@ const assert = require('assert') const EdgeEncryptor = require('../../../app/scripts/edge-encryptor') -var password = 'passw0rd1' -var data = 'some random data' +const password = 'passw0rd1' +const data = 'some random data' global.crypto = global.crypto || { getRandomValues: function (array) { diff --git a/test/unit/app/message-manager-test.js b/test/unit/app/message-manager-test.js index 36ef6c29f..b824acfe7 100644 --- a/test/unit/app/message-manager-test.js +++ b/test/unit/app/message-manager-test.js @@ -10,7 +10,7 @@ describe('Message Manager', function () { describe('#getMsgList', function () { it('when new should return empty array', function () { - var result = messageManager.messages + const result = messageManager.messages assert.ok(Array.isArray(result)) assert.equal(result.length, 0) }) @@ -21,9 +21,9 @@ describe('Message Manager', function () { describe('#addMsg', function () { it('adds a Msg returned in getMsgList', function () { - var Msg = { id: 1, status: 'approved', metamaskNetworkId: 'unit test' } + const Msg = { id: 1, status: 'approved', metamaskNetworkId: 'unit test' } messageManager.addMsg(Msg) - var result = messageManager.messages + const result = messageManager.messages assert.ok(Array.isArray(result)) assert.equal(result.length, 1) assert.equal(result[0].id, 1) @@ -32,10 +32,10 @@ describe('Message Manager', function () { describe('#setMsgStatusApproved', function () { it('sets the Msg status to approved', function () { - var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } + const Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } messageManager.addMsg(Msg) messageManager.setMsgStatusApproved(1) - var result = messageManager.messages + const result = messageManager.messages assert.ok(Array.isArray(result)) assert.equal(result.length, 1) assert.equal(result[0].status, 'approved') @@ -44,10 +44,10 @@ describe('Message Manager', function () { describe('#rejectMsg', function () { it('sets the Msg status to rejected', function () { - var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } + const Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } messageManager.addMsg(Msg) messageManager.rejectMsg(1) - var result = messageManager.messages + const result = messageManager.messages assert.ok(Array.isArray(result)) assert.equal(result.length, 1) assert.equal(result[0].status, 'rejected') @@ -59,7 +59,7 @@ describe('Message Manager', function () { messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }) messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' }) messageManager._updateMsg({ id: '1', status: 'blah', hash: 'foo', metamaskNetworkId: 'unit test' }) - var result = messageManager.getMsg('1') + const result = messageManager.getMsg('1') assert.equal(result.hash, 'foo') }) }) diff --git a/test/unit/app/nodeify-test.js b/test/unit/app/nodeify-test.js index fa5e49fb2..fc06803db 100644 --- a/test/unit/app/nodeify-test.js +++ b/test/unit/app/nodeify-test.js @@ -33,7 +33,9 @@ describe('nodeify', function () { }) it('no callback - should asyncly throw an error if underlying function does', function (done) { - const nodified = nodeify(async () => { throw new Error('boom!') }, obj) + const nodified = nodeify(async () => { + throw new Error('boom!') + }, obj) process.prependOnceListener('uncaughtException', function (err) { assert.ok(err, 'got expected error') assert.ok(err.message.includes('boom!'), 'got expected error message') @@ -50,7 +52,9 @@ describe('nodeify', function () { const nodified = nodeify(() => 42) try { nodified((err, result) => { - if (err) return done(new Error(`should not have thrown any error: ${err.message}`)) + if (err) { + return done(new Error(`should not have thrown any error: ${err.message}`)) + } assert.equal(42, result, 'got expected result') }) done() @@ -60,10 +64,14 @@ describe('nodeify', function () { }) it('sync functions - handles errors', function (done) { - const nodified = nodeify(() => { throw new Error('boom!') }) + const nodified = nodeify(() => { + throw new Error('boom!') + }) try { nodified((err, result) => { - if (result) return done(new Error('should not have returned any result')) + if (result) { + return done(new Error('should not have returned any result')) + } assert.ok(err, 'got expected error') assert.ok(err.message.includes('boom!'), 'got expected error message') }) diff --git a/test/unit/app/personal-message-manager-test.js b/test/unit/app/personal-message-manager-test.js index b07167bff..52cbdb75c 100644 --- a/test/unit/app/personal-message-manager-test.js +++ b/test/unit/app/personal-message-manager-test.js @@ -11,7 +11,7 @@ describe('Personal Message Manager', function () { describe('#getMsgList', function () { it('when new should return empty array', function () { - var result = messageManager.messages + const result = messageManager.messages assert.ok(Array.isArray(result)) assert.equal(result.length, 0) }) @@ -22,9 +22,9 @@ describe('Personal Message Manager', function () { describe('#addMsg', function () { it('adds a Msg returned in getMsgList', function () { - var Msg = { id: 1, status: 'approved', metamaskNetworkId: 'unit test' } + const Msg = { id: 1, status: 'approved', metamaskNetworkId: 'unit test' } messageManager.addMsg(Msg) - var result = messageManager.messages + const result = messageManager.messages assert.ok(Array.isArray(result)) assert.equal(result.length, 1) assert.equal(result[0].id, 1) @@ -33,10 +33,10 @@ describe('Personal Message Manager', function () { describe('#setMsgStatusApproved', function () { it('sets the Msg status to approved', function () { - var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } + const Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } messageManager.addMsg(Msg) messageManager.setMsgStatusApproved(1) - var result = messageManager.messages + const result = messageManager.messages assert.ok(Array.isArray(result)) assert.equal(result.length, 1) assert.equal(result[0].status, 'approved') @@ -45,10 +45,10 @@ describe('Personal Message Manager', function () { describe('#rejectMsg', function () { it('sets the Msg status to rejected', function () { - var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } + const Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } messageManager.addMsg(Msg) messageManager.rejectMsg(1) - var result = messageManager.messages + const result = messageManager.messages assert.ok(Array.isArray(result)) assert.equal(result.length, 1) assert.equal(result[0].status, 'rejected') @@ -60,7 +60,7 @@ describe('Personal Message Manager', function () { messageManager.addMsg({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }) messageManager.addMsg({ id: '2', status: 'approved', metamaskNetworkId: 'unit test' }) messageManager._updateMsg({ id: '1', status: 'blah', hash: 'foo', metamaskNetworkId: 'unit test' }) - var result = messageManager.getMsg('1') + const result = messageManager.getMsg('1') assert.equal(result.hash, 'foo') }) }) @@ -87,20 +87,20 @@ describe('Personal Message Manager', function () { describe('#normalizeMsgData', function () { it('converts text to a utf8 hex string', function () { - var input = 'hello' - var output = messageManager.normalizeMsgData(input) + const input = 'hello' + const output = messageManager.normalizeMsgData(input) assert.equal(output, '0x68656c6c6f', 'predictably hex encoded') }) it('tolerates a hex prefix', function () { - var input = '0x12' - var output = messageManager.normalizeMsgData(input) + const input = '0x12' + const output = messageManager.normalizeMsgData(input) assert.equal(output, '0x12', 'un modified') }) it('tolerates normal hex', function () { - var input = '12' - var output = messageManager.normalizeMsgData(input) + const input = '12' + const output = messageManager.normalizeMsgData(input) assert.equal(output, '0x12', 'adds prefix') }) }) diff --git a/test/unit/app/typed-message-manager.spec.js b/test/unit/app/typed-message-manager.spec.js index 3db92d4a3..959662b91 100644 --- a/test/unit/app/typed-message-manager.spec.js +++ b/test/unit/app/typed-message-manager.spec.js @@ -30,12 +30,12 @@ describe('Typed Message Manager', () => { 'EIP712Domain': [ {'name': 'name', 'type': 'string' }, {'name': 'version', 'type': 'string' }, - {'name': 'chainId', ' type': 'uint256' }, - {'name': 'verifyingContract', ' type': 'address' }, + {'name': 'chainId', 'type': 'uint256' }, + {'name': 'verifyingContract', 'type': 'address' }, ], 'Person': [ {'name': 'name', 'type': 'string' }, - {'name': 'wallet', ' type': 'address' }, + {'name': 'wallet', 'type': 'address' }, ], 'Mail': [ {'name': 'from', 'type': 'Person' }, @@ -64,7 +64,7 @@ describe('Typed Message Manager', () => { }), } - typedMessageManager.addUnapprovedMessage(msgParamsV3, 'V3') + typedMessageManager.addUnapprovedMessage(msgParamsV3, null, 'V3') typedMsgs = typedMessageManager.getUnapprovedMsgs() messages = typedMessageManager.messages msgId = Object.keys(typedMsgs)[0] @@ -73,7 +73,7 @@ describe('Typed Message Manager', () => { }) it('supports version 1 of signedTypedData', () => { - typedMessageManager.addUnapprovedMessage(msgParamsV1, 'V1') + typedMessageManager.addUnapprovedMessage(msgParamsV1, null, 'V1') assert.equal(messages[messages.length - 1].msgParams.data, msgParamsV1.data) }) @@ -87,7 +87,7 @@ describe('Typed Message Manager', () => { it('validates params', function () { assert.doesNotThrow(() => { - typedMessageManager.validateParams(messages[0]) + typedMessageManager.validateParams(messages[0].msgParams) }, 'Does not throw with valid parameters') }) diff --git a/test/unit/migrations/023-test.js b/test/unit/migrations/023-test.js index 1b47dea92..7c93feefe 100644 --- a/test/unit/migrations/023-test.js +++ b/test/unit/migrations/023-test.js @@ -37,7 +37,9 @@ let nonDeletableCount = 0 let status while (transactions.length <= 100) { status = txStates[Math.floor(Math.random() * Math.floor(txStates.length - 1))] - if (!deletableTxStates.find((s) => s === status)) nonDeletableCount++ + if (!deletableTxStates.find((s) => s === status)) { + nonDeletableCount++ + } transactions.push({status}) } diff --git a/test/unit/migrations/024-test.js b/test/unit/migrations/024-test.js index 671c7f832..9a6a6661a 100644 --- a/test/unit/migrations/024-test.js +++ b/test/unit/migrations/024-test.js @@ -31,8 +31,11 @@ describe('storage is migrated successfully and the txParams.from are lowercase', .then((migratedData) => { const migratedTransactions = migratedData.data.TransactionController.transactions migratedTransactions.forEach((tx) => { - if (tx.status === 'unapproved') assert.equal(tx.txParams.from, '0x8acce2391c0d510a6c5e5d8f819a678f79b7e675') - else assert.equal(tx.txParams.from, '0x8aCce2391c0d510a6c5E5d8f819a678f79b7e675') + if (tx.status === 'unapproved') { + assert.equal(tx.txParams.from, '0x8acce2391c0d510a6c5e5d8f819a678f79b7e675') + } else { + assert.equal(tx.txParams.from, '0x8aCce2391c0d510a6c5E5d8f819a678f79b7e675') + } }) done() }).catch(done) diff --git a/test/unit/migrations/025-test.js b/test/unit/migrations/025-test.js index fa89bc54f..cabaecd41 100644 --- a/test/unit/migrations/025-test.js +++ b/test/unit/migrations/025-test.js @@ -32,8 +32,12 @@ describe('storage is migrated successfully and the txParams.from are lowercase', .then((migratedData) => { const migratedTransactions = migratedData.data.TransactionController.transactions migratedTransactions.forEach((tx) => { - if (tx.status === 'unapproved') assert(!tx.txParams.random) - if (tx.status === 'unapproved') assert(!tx.txParams.chainId) + if (tx.status === 'unapproved') { + assert(!tx.txParams.random) + } + if (tx.status === 'unapproved') { + assert(!tx.txParams.chainId) + } }) done() }).catch(done) diff --git a/test/unit/migrations/027-test.js b/test/unit/migrations/027-test.js index 3ec9f0c0e..767243a25 100644 --- a/test/unit/migrations/027-test.js +++ b/test/unit/migrations/027-test.js @@ -30,7 +30,9 @@ describe('migration #27', () => { const newTransactions = newStorage.data.TransactionController.transactions assert.equal(newTransactions.length, 6, 'transactions is expected to have the length of 6') newTransactions.forEach((txMeta) => { - if (txMeta.status === 'rejected') done(new Error('transaction was found with a status of rejected')) + if (txMeta.status === 'rejected') { + done(new Error('transaction was found with a status of rejected')) + } }) done() }) diff --git a/test/unit/migrations/029-test.js b/test/unit/migrations/029-test.js index 7f9b8a005..ca0996389 100644 --- a/test/unit/migrations/029-test.js +++ b/test/unit/migrations/029-test.js @@ -28,7 +28,9 @@ describe('storage is migrated successfully where transactions that are submitted assert(txMeta1.err.message.includes('too long'), 'error message assigned') txs.forEach((tx) => { - if (tx.id === 1) return + if (tx.id === 1) { + return + } assert.notEqual(tx.status, 'failed', 'other tx is not auto failed') }) diff --git a/test/unit/migrations/migrator-test.js b/test/unit/migrations/migrator-test.js index 3dcc5aff7..9a949def4 100644 --- a/test/unit/migrations/migrator-test.js +++ b/test/unit/migrations/migrator-test.js @@ -58,7 +58,9 @@ describe('Migrator', () => { it('should emit an error', function (done) { this.timeout(15000) - const migrator = new Migrator({ migrations: [{ version: 1, migrate: async () => { throw new Error('test') } } ] }) + const migrator = new Migrator({ migrations: [{ version: 1, migrate: async () => { + throw new Error('test') + } } ] }) migrator.on('error', () => done()) migrator.migrateData({ meta: {version: 0} }) .then(() => { diff --git a/test/unit/reducers/unlock_vault_test.js b/test/unit/reducers/unlock_vault_test.js index d66891a63..bb992ab40 100644 --- a/test/unit/reducers/unlock_vault_test.js +++ b/test/unit/reducers/unlock_vault_test.js @@ -1,11 +1,11 @@ // var jsdom = require('mocha-jsdom') -var assert = require('assert') +const assert = require('assert') // var freeze = require('deep-freeze-strict') -var path = require('path') -var sinon = require('sinon') +const path = require('path') +const sinon = require('sinon') -var actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'store', 'actions.js')) -var reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'ducks', 'index.js')) +const actions = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'store', 'actions.js')) +const reducers = require(path.join(__dirname, '..', '..', '..', 'ui', 'app', 'ducks', 'index.js')) describe('#unlockMetamask(selectedAccount)', function () { beforeEach(function () { diff --git a/test/unit/responsive/components/dropdown-test.js b/test/unit/responsive/components/dropdown-test.js index 1fadbfb60..2b9a5457b 100644 --- a/test/unit/responsive/components/dropdown-test.js +++ b/test/unit/responsive/components/dropdown-test.js @@ -1,6 +1,6 @@ +const React = require('react') const assert = require('assert') -const h = require('react-hyperscript') const sinon = require('sinon') const path = require('path') const Dropdown = require(path.join(__dirname, '..', '..', '..', '..', 'ui', 'app', 'components', 'app', 'dropdowns', 'index.js')).Dropdown @@ -39,23 +39,19 @@ describe('Dropdown components', function () { onClick = sinon.spy() store = createMockStore(mockState) - component = mountWithStore(h( - Dropdown, - dropdownComponentProps, - [ - h('style', ` - .drop-menu-item:hover { background:rgb(235, 235, 235); } - .drop-menu-item i { margin: 11px; } - `), - h('li', { - closeMenu, - onClick, - }, 'Item 1'), - h('li', { - closeMenu, - onClick, - }, 'Item 2'), - ] + component = mountWithStore(( + + +
  • Item 1
  • +
  • Item 2
  • +
    ), store) dropdownComponent = component }) diff --git a/test/unit/test-utils.js b/test/unit/test-utils.js index 7d0ae4d91..de508c504 100644 --- a/test/unit/test-utils.js +++ b/test/unit/test-utils.js @@ -10,7 +10,9 @@ async function assertRejects (asyncFn, regExp) { try { await asyncFn() } catch (error) { - f = () => { throw error } + f = () => { + throw error + } } finally { assert.throws(f, regExp) } diff --git a/test/unit/ui/app/actions.spec.js b/test/unit/ui/app/actions.spec.js index 9ed21e729..fffc14ab8 100644 --- a/test/unit/ui/app/actions.spec.js +++ b/test/unit/ui/app/actions.spec.js @@ -755,7 +755,6 @@ describe('Actions', () => { 'domain': { 'name': 'Ether Mainl', 'version': '1', - 'chainId': 1, 'verifyingContract': '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', }, 'message': { @@ -773,11 +772,12 @@ describe('Actions', () => { } beforeEach(() => { - metamaskController.newUnsignedTypedMessage(msgParamsV3, 'V3') + metamaskController.newUnsignedTypedMessage(msgParamsV3, null, 'V3') messages = metamaskController.typedMessageManager.getUnapprovedMsgs() typedMessages = metamaskController.typedMessageManager.messages msgId = Object.keys(messages)[0] typedMessages[0].msgParams.metamaskId = parseInt(msgId) + signTypedMsgSpy = sinon.stub(background, 'signTypedMessage') }) afterEach(() => { @@ -786,7 +786,6 @@ describe('Actions', () => { it('calls signTypedMsg in background with no error', () => { const store = mockStore() - signTypedMsgSpy = sinon.stub(background, 'signTypedMessage') store.dispatch(actions.signTypedMsg(msgParamsV3)) assert(signTypedMsgSpy.calledOnce) @@ -801,8 +800,6 @@ describe('Actions', () => { { type: 'DISPLAY_WARNING', value: 'error' }, ] - signTypedMsgSpy = sinon.stub(background, 'signTypedMessage') - signTypedMsgSpy.callsFake((_, callback) => { callback(new Error('error')) }) diff --git a/test/unit/util_test.js b/test/unit/util_test.js index 10bfc6c1a..bc1edecd5 100644 --- a/test/unit/util_test.js +++ b/test/unit/util_test.js @@ -1,13 +1,15 @@ -var assert = require('assert') -var sinon = require('sinon') +const assert = require('assert') +const sinon = require('sinon') const ethUtil = require('ethereumjs-util') -var path = require('path') -var util = require(path.join(__dirname, '..', '..', 'ui', 'app', 'helpers', 'utils', 'util.js')) +const path = require('path') +const util = require(path.join(__dirname, '..', '..', 'ui', 'app', 'helpers', 'utils', 'util.js')) describe('util', function () { - var ethInWei = '1' - for (var i = 0; i < 18; i++) { ethInWei += '0' } + let ethInWei = '1' + for (let i = 0; i < 18; i++) { + ethInWei += '0' + } beforeEach(function () { this.sinon = sinon.createSandbox() @@ -45,52 +47,52 @@ describe('util', function () { describe('#addressSummary', function () { it('should add case-sensitive checksum', function () { - var address = '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825' - var result = util.addressSummary(address) + const address = '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825' + const result = util.addressSummary(address) assert.equal(result, '0xFDEa65C8...b825') }) it('should accept arguments for firstseg, lastseg, and keepPrefix', function () { - var address = '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825' - var result = util.addressSummary(address, 4, 4, false) + const address = '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825' + const result = util.addressSummary(address, 4, 4, false) assert.equal(result, 'FDEa...b825') }) }) describe('#isValidAddress', function () { it('should allow 40-char non-prefixed hex', function () { - var address = 'fdea65c8e26263f6d9a1b5de9555d2931a33b825' - var result = util.isValidAddress(address) + const address = 'fdea65c8e26263f6d9a1b5de9555d2931a33b825' + const result = util.isValidAddress(address) assert.ok(result) }) it('should allow 42-char non-prefixed hex', function () { - var address = '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825' - var result = util.isValidAddress(address) + const address = '0xfdea65c8e26263f6d9a1b5de9555d2931a33b825' + const result = util.isValidAddress(address) assert.ok(result) }) it('should not allow less non hex-prefixed', function () { - var address = 'fdea65c8e26263f6d9a1b5de9555d2931a33b85' - var result = util.isValidAddress(address) + const address = 'fdea65c8e26263f6d9a1b5de9555d2931a33b85' + const result = util.isValidAddress(address) assert.ok(!result) }) it('should not allow less hex-prefixed', function () { - var address = '0xfdea65ce26263f6d9a1b5de9555d2931a33b85' - var result = util.isValidAddress(address) + const address = '0xfdea65ce26263f6d9a1b5de9555d2931a33b85' + const result = util.isValidAddress(address) assert.ok(!result) }) it('should recognize correct capitalized checksum', function () { - var address = '0xFDEa65C8e26263F6d9A1B5de9555D2931A33b825' - var result = util.isValidAddress(address) + const address = '0xFDEa65C8e26263F6d9A1B5de9555D2931A33b825' + const result = util.isValidAddress(address) assert.ok(result) }) it('should recognize incorrect capitalized checksum', function () { - var address = '0xFDea65C8e26263F6d9A1B5de9555D2931A33b825' - var result = util.isValidAddress(address) + const address = '0xFDea65C8e26263F6d9A1B5de9555D2931A33b825' + const result = util.isValidAddress(address) assert.ok(!result) }) @@ -163,58 +165,58 @@ describe('util', function () { describe('#numericBalance', function () { it('should return a BN 0 if given nothing', function () { - var result = util.numericBalance() + const result = util.numericBalance() assert.equal(result.toString(10), 0) }) it('should work with hex prefix', function () { - var result = util.numericBalance('0x012') + const result = util.numericBalance('0x012') assert.equal(result.toString(10), '18') }) it('should work with no hex prefix', function () { - var result = util.numericBalance('012') + const result = util.numericBalance('012') assert.equal(result.toString(10), '18') }) }) describe('#formatBalance', function () { it('when given nothing', function () { - var result = util.formatBalance() + const result = util.formatBalance() assert.equal(result, 'None', 'should return "None"') }) it('should return eth as string followed by ETH', function () { - var input = new ethUtil.BN(ethInWei, 10).toJSON() - var result = util.formatBalance(input, 4) + const input = new ethUtil.BN(ethInWei, 10).toJSON() + const result = util.formatBalance(input, 4) assert.equal(result, '1.0000 ETH') }) it('should return eth as string followed by ETH', function () { - var input = new ethUtil.BN(ethInWei, 10).div(new ethUtil.BN('2', 10)).toJSON() - var result = util.formatBalance(input, 3) + const input = new ethUtil.BN(ethInWei, 10).div(new ethUtil.BN('2', 10)).toJSON() + const result = util.formatBalance(input, 3) assert.equal(result, '0.500 ETH') }) it('should display specified decimal points', function () { - var input = '0x128dfa6a90b28000' - var result = util.formatBalance(input, 2) + const input = '0x128dfa6a90b28000' + const result = util.formatBalance(input, 2) assert.equal(result, '1.33 ETH') }) it('should default to 3 decimal points', function () { - var input = '0x128dfa6a90b28000' - var result = util.formatBalance(input) + const input = '0x128dfa6a90b28000' + const result = util.formatBalance(input) assert.equal(result, '1.337 ETH') }) it('should show 2 significant digits for tiny balances', function () { - var input = '0x1230fa6a90b28' - var result = util.formatBalance(input) + const input = '0x1230fa6a90b28' + const result = util.formatBalance(input) assert.equal(result, '0.00032 ETH') }) it('should not parse the balance and return value with 2 decimal points with ETH at the end', function () { - var value = '1.2456789' - var needsParse = false - var result = util.formatBalance(value, 2, needsParse) + const value = '1.2456789' + const needsParse = false + const result = util.formatBalance(value, 2, needsParse) assert.equal(result, '1.24 ETH') }) }) @@ -222,7 +224,7 @@ describe('util', function () { describe('normalizing values', function () { describe('#normalizeToWei', function () { it('should convert an eth to the appropriate equivalent values', function () { - var valueTable = { + const valueTable = { wei: '1000000000000000000', kwei: '1000000000000000', mwei: '1000000000000', @@ -237,11 +239,11 @@ describe('util', function () { // gether:'0.000000001', // tether:'0.000000000001', } - var oneEthBn = new ethUtil.BN(ethInWei, 10) + const oneEthBn = new ethUtil.BN(ethInWei, 10) - for (var currency in valueTable) { - var value = new ethUtil.BN(valueTable[currency], 10) - var output = util.normalizeToWei(value, currency) + for (const currency in valueTable) { + const value = new ethUtil.BN(valueTable[currency], 10) + const output = util.normalizeToWei(value, currency) assert.equal(output.toString(10), valueTable.wei, `value of ${output.toString(10)} ${currency} should convert to ${oneEthBn}`) } }) @@ -249,66 +251,66 @@ describe('util', function () { describe('#normalizeEthStringToWei', function () { it('should convert decimal eth to pure wei BN', function () { - var input = '1.23456789' - var output = util.normalizeEthStringToWei(input) + const input = '1.23456789' + const output = util.normalizeEthStringToWei(input) assert.equal(output.toString(10), '1234567890000000000') }) it('should convert 1 to expected wei', function () { - var input = '1' - var output = util.normalizeEthStringToWei(input) + const input = '1' + const output = util.normalizeEthStringToWei(input) assert.equal(output.toString(10), ethInWei) }) it('should account for overflow numbers gracefully by dropping extra precision.', function () { - var input = '1.11111111111111111111' - var output = util.normalizeEthStringToWei(input) + const input = '1.11111111111111111111' + const output = util.normalizeEthStringToWei(input) assert.equal(output.toString(10), '1111111111111111111') }) it('should not truncate very exact wei values that do not have extra precision.', function () { - var input = '1.100000000000000001' - var output = util.normalizeEthStringToWei(input) + const input = '1.100000000000000001' + const output = util.normalizeEthStringToWei(input) assert.equal(output.toString(10), '1100000000000000001') }) }) describe('#normalizeNumberToWei', function () { it('should handle a simple use case', function () { - var input = 0.0002 - var output = util.normalizeNumberToWei(input, 'ether') - var str = output.toString(10) + const input = 0.0002 + const output = util.normalizeNumberToWei(input, 'ether') + const str = output.toString(10) assert.equal(str, '200000000000000') }) it('should convert a kwei number to the appropriate equivalent wei', function () { - var result = util.normalizeNumberToWei(1.111, 'kwei') + const result = util.normalizeNumberToWei(1.111, 'kwei') assert.equal(result.toString(10), '1111', 'accepts decimals') }) it('should convert a ether number to the appropriate equivalent wei', function () { - var result = util.normalizeNumberToWei(1.111, 'ether') + const result = util.normalizeNumberToWei(1.111, 'ether') assert.equal(result.toString(10), '1111000000000000000', 'accepts decimals') }) }) describe('#isHex', function () { it('should return true when given a hex string', function () { - var result = util.isHex('c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2') + const result = util.isHex('c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2') assert(result) }) it('should return false when given a non-hex string', function () { - var result = util.isHex('c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714imnotreal') + const result = util.isHex('c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714imnotreal') assert(!result) }) it('should return false when given a string containing a non letter/number character', function () { - var result = util.isHex('c3ab8ff13720!8ad9047dd39466b3c%8974e592c2fa383d4a396071imnotreal') + const result = util.isHex('c3ab8ff13720!8ad9047dd39466b3c%8974e592c2fa383d4a396071imnotreal') assert(!result) }) it('should return true when given a hex string with hex-prefix', function () { - var result = util.isHex('0xc3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2') + const result = util.isHex('0xc3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2') assert(result) }) }) diff --git a/test/web3/schema.js b/test/web3/schema.js index 336060431..9dc528a57 100644 --- a/test/web3/schema.js +++ b/test/web3/schema.js @@ -1,6 +1,6 @@ /* eslint no-unused-vars: 0 */ -var params = { +const params = { // diffrent params used in the methods param: [], blockHashParams: '0xb3b20624f8f0f86eb50dd04688409e5cea4bd02d700bf6e79e9384d47d6a5a35', @@ -93,7 +93,7 @@ var params = { }, } -var methods = { +const methods = { hexaNumberMethods: { // these are the methods which have output in the form of hexa decimal numbers eth_blockNumber: ['eth_blockNumber', params.param, 'Q'], diff --git a/test/web3/web3.js b/test/web3/web3.js index 8f319f38e..3431ee8ba 100644 --- a/test/web3/web3.js +++ b/test/web3/web3.js @@ -1,6 +1,6 @@ /* eslint no-undef: 0 */ -var json = methods +const json = methods web3.currentProvider.enable().then(() => { diff --git a/ui/app/components/app/account-details/account-details.component.js b/ui/app/components/app/account-details/account-details.component.js index 55078cee0..e75e777a6 100644 --- a/ui/app/components/app/account-details/account-details.component.js +++ b/ui/app/components/app/account-details/account-details.component.js @@ -19,9 +19,11 @@ export default class AccountDetails extends Component { static propTypes = { hideSidebar: PropTypes.func, showAccountDetailModal: PropTypes.func, + showConnectedSites: PropTypes.func.isRequired, label: PropTypes.string.isRequired, checksummedAddress: PropTypes.string.isRequired, name: PropTypes.string.isRequired, + history: PropTypes.object.isRequired, } state = { @@ -48,6 +50,7 @@ export default class AccountDetails extends Component { const { hideSidebar, showAccountDetailModal, + showConnectedSites, label, checksummedAddress, name, @@ -65,14 +68,19 @@ export default class AccountDetails extends Component {
    {label}
    -
    - +
    + {name} - +
    + + +
    list.concat(keyring.accounts), []) @@ -70,6 +75,8 @@ export default class AccountMenu extends PureComponent { const keyring = keyrings.find(kr => { return kr.accounts.includes(simpleAddress) || kr.accounts.includes(identity.address) }) + const addressDomains = addressConnectedDomainMap[identity.address] || {} + const iconAndNameForOpenDomain = addressDomains[originOfCurrentTab] return (
    + { iconAndNameForOpenDomain + ? ( +
    + +
    + ) + : null + } { this.renderKeyringType(keyring) } { this.renderRemoveAccount(keyring, identity) }
    @@ -155,9 +170,11 @@ export default class AccountMenu extends PureComponent { case 'Simple Key Pair': label = t('imported') break + default: + return null } - return label && ( + return (
    { label }
    @@ -250,12 +267,12 @@ export default class AccountMenu extends PureComponent { }) history.push(NEW_ACCOUNT_ROUTE) }} - icon={ + icon={( - } + )} text={t('createAccount')} /> - } + )} text={t('importAccount')} /> - } + )} text={t('connectHardwareWallet')} /> @@ -325,12 +342,12 @@ export default class AccountMenu extends PureComponent { }, }) }} - icon={ + icon={( - } + )} text={t('settings')} /> diff --git a/ui/app/components/app/account-menu/account-menu.container.js b/ui/app/components/app/account-menu/account-menu.container.js index ae2e28e76..00a0666ec 100644 --- a/ui/app/components/app/account-menu/account-menu.container.js +++ b/ui/app/components/app/account-menu/account-menu.container.js @@ -11,7 +11,12 @@ import { showInfoPage, showModal, } from '../../../store/actions' -import { getMetaMaskAccounts } from '../../../selectors/selectors' +import { + getAddressConnectedDomainMap, + getMetaMaskAccounts, + getOriginOfCurrentTab, +} from '../../../selectors/selectors' + import AccountMenu from './account-menu.component' function mapStateToProps (state) { @@ -23,6 +28,8 @@ function mapStateToProps (state) { keyrings, identities, accounts: getMetaMaskAccounts(state), + addressConnectedDomainMap: getAddressConnectedDomainMap(state), + originOfCurrentTab: getOriginOfCurrentTab(state), } } diff --git a/ui/app/components/app/account-menu/index.scss b/ui/app/components/app/account-menu/index.scss index 614e19104..93b9766d3 100644 --- a/ui/app/components/app/account-menu/index.scss +++ b/ui/app/components/app/account-menu/index.scss @@ -175,4 +175,8 @@ opacity: 1; } } + + &__icon-list { + display: flex; + } } diff --git a/ui/app/components/app/account-panel.js b/ui/app/components/app/account-panel.js index e61cb8ad6..3ffd97887 100644 --- a/ui/app/components/app/account-panel.js +++ b/ui/app/components/app/account-panel.js @@ -1,6 +1,5 @@ +import React, { Component } from 'react' const inherits = require('util').inherits -const Component = require('react').Component -const h = require('react-hyperscript') import Identicon from '../ui/identicon' const formatBalance = require('../../helpers/utils/util').formatBalance const addressSummary = require('../../helpers/utils/util').addressSummary @@ -14,12 +13,12 @@ function AccountPanel () { } AccountPanel.prototype.render = function () { - var state = this.props - var identity = state.identity || {} - var account = state.account || {} - var isFauceting = state.isFauceting + const state = this.props + const identity = state.identity || {} + const account = state.account || {} + const isFauceting = state.isFauceting - var panelState = { + const panelState = { key: `accountPanel${identity.address}`, identiconKey: identity.address, identiconLabel: identity.name || '', @@ -33,39 +32,24 @@ AccountPanel.prototype.render = function () { } return ( - - h('.identity-panel.flex-row.flex-space-between', { - style: { - flex: '1 0 auto', - cursor: panelState.onClick ? 'pointer' : undefined, - }, - onClick: panelState.onClick, - }, [ - - // account identicon - h('.identicon-wrapper.flex-column.select-none', [ - h(Identicon, { - address: panelState.identiconKey, - imageify: state.imageifyIdenticons, - }), - h('span.font-small', panelState.identiconLabel.substring(0, 7) + '...'), - ]), - - // account address, balance - h('.identity-data.flex-column.flex-justify-center.flex-grow.select-none', [ - - panelState.attributes.map((attr) => { - return h('.flex-row.flex-space-between', { - key: '' + Math.round(Math.random() * 1000000), - }, [ - h('label.font-small.no-select', attr.key), - h('span.font-small', attr.value), - ]) - }), - ]), - - ]) - +
    +
    + + {panelState.identiconLabel.substring(0, 7) + '...'} +
    +
    + {panelState.attributes.map((attr) => ( +
    + + {attr.value} +
    + ))} +
    +
    ) } diff --git a/ui/app/components/app/app-header/app-header.component.js b/ui/app/components/app/app-header/app-header.component.js index e1bc0cf24..bd01558c5 100644 --- a/ui/app/components/app/app-header/app-header.component.js +++ b/ui/app/components/app/app-header/app-header.component.js @@ -89,7 +89,8 @@ export default class AppHeader extends PureComponent { return (
    + className={classnames('app-header', { 'app-header--back-drop': isUnlocked })} + >
    { - this.updateValidity(event) - }, - onChange: (event) => { - this.updateValidity(event) - const value = (event.target.value === '') ? '' : event.target.value - - - const scaledNumber = this.upsize(value, scale, precision) - const precisionBN = new BN(scaledNumber, 10) - onChange(precisionBN, event.target.checkValidity()) - }, - onInvalid: (event) => { - const msg = this.constructWarning() - if (msg === state.invalid) { - return - } - this.setState({ invalid: msg }) - event.preventDefault() - return false - }, - }), - h('div', { - style: { - color: ' #AEAEAE', - fontSize: '12px', - marginLeft: '5px', - marginRight: '6px', - width: '20px', - }, - }, suffix), - ]), - - state.invalid ? h('span.error', { - style: { - position: 'absolute', - right: '0px', - textAlign: 'right', - transform: 'translateY(26px)', - padding: '3px', - background: 'rgba(255,255,255,0.85)', - zIndex: '1', - textTransform: 'capitalize', - border: '2px solid #E20202', - }, - }, state.invalid) : null, - ]) - ) -} - -BnAsDecimalInput.prototype.setValid = function () { - this.setState({ invalid: null }) -} - -BnAsDecimalInput.prototype.updateValidity = function (event) { - const target = event.target - const value = this.props.value - const newValue = target.value - - if (value === newValue) { - return - } - - const valid = target.checkValidity() - - if (valid) { - this.setState({ invalid: null }) - } -} - -BnAsDecimalInput.prototype.constructWarning = function () { - const { name, min, max, scale, suffix } = this.props - const newMin = min && this.downsize(min.toString(10), scale) - const newMax = max && this.downsize(max.toString(10), scale) - let message = name ? name + ' ' : '' - - if (min && max) { - message += this.context.t('betweenMinAndMax', [`${newMin} ${suffix}`, `${newMax} ${suffix}`]) - } else if (min) { - message += this.context.t('greaterThanMin', [`${newMin} ${suffix}`]) - } else if (max) { - message += this.context.t('lessThanMax', [`${newMax} ${suffix}`]) - } else { - message += this.context.t('invalidInput') - } - - return message -} - - -BnAsDecimalInput.prototype.downsize = function (number, scale) { - // if there is no scaling, simply return the number - if (scale === 0) { - return Number(number) - } else { - // if the scale is the same as the precision, account for this edge case. - var adjustedNumber = number - while (adjustedNumber.length < scale) { - adjustedNumber = '0' + adjustedNumber - } - return Number(adjustedNumber.slice(0, -scale) + '.' + adjustedNumber.slice(-scale)) - } -} - -BnAsDecimalInput.prototype.upsize = function (number, scale, precision) { - var stringArray = number.toString().split('.') - var decimalLength = stringArray[1] ? stringArray[1].length : 0 - var newString = stringArray[0] - - // If there is scaling and decimal parts exist, integrate them in. - if ((scale !== 0) && (decimalLength !== 0)) { - newString += stringArray[1].slice(0, precision) - } - - // Add 0s to account for the upscaling. - for (var i = decimalLength; i < scale; i++) { - newString += '0' - } - return newString -} diff --git a/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js b/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js index 0cc4d8262..b973ad84d 100644 --- a/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js +++ b/ui/app/components/app/confirm-page-container/confirm-page-container-content/confirm-page-container-summary/confirm-page-container-summary.component.js @@ -47,9 +47,11 @@ const ConfirmPageContainerSummary = props => {
    { - hideSubtitle ||
    - { subtitleComponent || subtitle } -
    + hideSubtitle || ( +
    + { subtitleComponent || subtitle } +
    + ) } ) diff --git a/ui/app/components/app/confirm-page-container/confirm-page-container-header/confirm-page-container-header.component.js b/ui/app/components/app/confirm-page-container/confirm-page-container-header/confirm-page-container-header.component.js index 898d59068..4b91b4ddb 100644 --- a/ui/app/components/app/confirm-page-container/confirm-page-container-header/confirm-page-container-header.component.js +++ b/ui/app/components/app/confirm-page-container/confirm-page-container-header/confirm-page-container-header.component.js @@ -34,36 +34,40 @@ export default class ConfirmPageContainerHeader extends Component { return (
    { !showAccountInHeader - ?
    - - onEdit()} + ? ( +
    - { this.context.t('edit') } - -
    + + onEdit()} + > + { this.context.t('edit') } + +
    + ) : null } { showAccountInHeader - ?
    -
    - + ? ( +
    +
    + +
    +
    + { addressSlicer(accountAddress) } +
    -
    - { addressSlicer(accountAddress) } -
    -
    + ) : null } { !isFullScreen && } diff --git a/ui/app/components/app/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js b/ui/app/components/app/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js index c24d24b17..dd1fd2da4 100755 --- a/ui/app/components/app/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js +++ b/ui/app/components/app/confirm-page-container/confirm-page-container-navigation/confirm-page-container-navigation.component.js @@ -5,23 +5,28 @@ const ConfirmPageContainerNavigation = props => { const { onNextTx, totalTx, positionOfCurrentTx, nextTxId, prevTxId, showNavigation, firstTx, lastTx, ofText, requestsWaitingText } = props return ( -
    -
    + }} + >
    onNextTx(firstTx)}> + onClick={() => onNextTx(firstTx)} + >
    onNextTx(prevTxId)}> + onClick={() => onNextTx(prevTxId)} + >
    @@ -37,15 +42,18 @@ const ConfirmPageContainerNavigation = props => { className="confirm-page-container-navigation__container" style={{ visibility: nextTxId ? 'initial' : 'hidden', - }}> + }} + >
    onNextTx(nextTxId)}> + onClick={() => onNextTx(nextTxId)} + >
    onNextTx(lastTx)}> + onClick={() => onNextTx(lastTx)} + >
    diff --git a/ui/app/components/app/confirm-page-container/confirm-page-container.component.js b/ui/app/components/app/confirm-page-container/confirm-page-container.component.js index 86ce2bff7..9ebae3d95 100644 --- a/ui/app/components/app/confirm-page-container/confirm-page-container.component.js +++ b/ui/app/components/app/confirm-page-container/confirm-page-container.component.js @@ -133,15 +133,17 @@ export default class ConfirmPageContainer extends Component { > { hideSenderToRecipient ? null - : + : ( + + ) } { diff --git a/ui/app/components/app/connected-sites-list/connected-sites-list.component.js b/ui/app/components/app/connected-sites-list/connected-sites-list.component.js new file mode 100644 index 000000000..a04f87486 --- /dev/null +++ b/ui/app/components/app/connected-sites-list/connected-sites-list.component.js @@ -0,0 +1,157 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import classnames from 'classnames' +import Button from '../../ui/button' +import IconWithFallBack from '../../ui/icon-with-fallback' + +export default class ConnectedSitesList extends Component { + static contextTypes = { + t: PropTypes.func, + } + + static propTypes = { + renderableDomains: PropTypes.arrayOf(PropTypes.shape({ + name: PropTypes.string, + icon: PropTypes.string, + key: PropTypes.string, + lastConnectedTime: PropTypes.string, + permissionDescriptions: PropTypes.array, + })).isRequired, + domains: PropTypes.object, + showDisconnectAccountModal: PropTypes.func.isRequired, + showDisconnectAllModal: PropTypes.func.isRequired, + tabToConnect: PropTypes.object, + legacyExposeAccounts: PropTypes.func.isRequired, + selectedAddress: PropTypes.string.isRequired, + getOpenMetamaskTabsIds: PropTypes.func.isRequired, + } + + state = { + expandedDomain: '', + } + + componentWillMount () { + const { getOpenMetamaskTabsIds } = this.props + getOpenMetamaskTabsIds() + } + + handleDomainItemClick (domainKey) { + if (this.state.expandedDomain === domainKey) { + this.setState({ expandedDomain: '' }) + } else { + this.setState({ expandedDomain: domainKey }) + } + } + + render () { + const { + renderableDomains, + domains, + showDisconnectAccountModal, + showDisconnectAllModal, + tabToConnect, + legacyExposeAccounts, + selectedAddress, + } = this.props + const { expandedDomain } = this.state + const { t } = this.context + + return ( +
    + { + renderableDomains.map((domain, domainIndex) => { + const domainIsExpanded = expandedDomain === domain.key + return ( +
    +
    this.handleDomainItemClick(domain.key) }> +
    + + +
    +
    +
    + { domain.extensionId ? t('externalExtension') : domain.name } +
    +
    + { domain.lastConnectedTime + ? ( +
    + { t('domainLastConnect', [domain.lastConnectedTime]) } +
    + ) + : null + } + {domainIsExpanded + ? ( +
    + { domain.extensionId ? t('extensionId', [domain.extensionId]) : domain.secondaryName } +
    + ) + : null + } +
    +
    +
    + { domainIsExpanded ? : } +
    +
    + { domainIsExpanded + ? ( +
    +
    + { + domain.permissionDescriptions.map((description, pdIndex) => { + return ( +
    + +
    + { description } +
    +
    + ) + }) + } +
    +
    showDisconnectAccountModal(domain.key, domains[domain.key]) } + > + { t('disconnectAccount') } +
    +
    + ) + : null + } +
    + ) + }) + } +
    +
    + +
    + { tabToConnect + ? ( +
    + +
    + ) + : null + } +
    +
    + ) + } +} diff --git a/ui/app/components/app/connected-sites-list/connected-sites-list.container.js b/ui/app/components/app/connected-sites-list/connected-sites-list.container.js new file mode 100644 index 000000000..de45831a9 --- /dev/null +++ b/ui/app/components/app/connected-sites-list/connected-sites-list.container.js @@ -0,0 +1,57 @@ +import { connect } from 'react-redux' +import { compose } from 'recompose' + +import ConnectedSitesList from './connected-sites-list.component' +import { + showModal, + legacyExposeAccounts, + getOpenMetamaskTabsIds, +} from '../../../store/actions' +import { + getRenderablePermissionsDomains, + getPermissionsDomains, + getAddressConnectedToCurrentTab, + getSelectedAddress, +} from '../../../selectors/selectors' +import { getOriginFromUrl } from '../../../helpers/utils/util' + +const mapStateToProps = state => { + const addressConnectedToCurrentTab = getAddressConnectedToCurrentTab(state) + const { openMetaMaskTabs } = state.appState + const { title, url, id } = state.activeTab + + let tabToConnect + + if (!addressConnectedToCurrentTab && url && !openMetaMaskTabs[id]) { + tabToConnect = { + title, + origin: getOriginFromUrl(url), + } + } + + return { + domains: getPermissionsDomains(state), + renderableDomains: getRenderablePermissionsDomains(state), + tabToConnect, + selectedAddress: getSelectedAddress(state), + } +} + +const mapDispatchToProps = dispatch => { + return { + showDisconnectAccountModal: (domainKey, domain) => { + dispatch(showModal({ name: 'DISCONNECT_ACCOUNT', domainKey, domain })) + }, + showDisconnectAllModal: () => { + dispatch(showModal({ name: 'DISCONNECT_ALL' })) + }, + legacyExposeAccounts: (origin, account) => { + dispatch(legacyExposeAccounts(origin, [account])) + }, + getOpenMetamaskTabsIds: () => dispatch(getOpenMetamaskTabsIds()), + } +} + +export default compose( + connect(mapStateToProps, mapDispatchToProps) +)(ConnectedSitesList) diff --git a/ui/app/components/app/connected-sites-list/index.js b/ui/app/components/app/connected-sites-list/index.js new file mode 100644 index 000000000..431a3a8eb --- /dev/null +++ b/ui/app/components/app/connected-sites-list/index.js @@ -0,0 +1 @@ +export { default } from './connected-sites-list.container' diff --git a/ui/app/components/app/connected-sites-list/index.scss b/ui/app/components/app/connected-sites-list/index.scss new file mode 100644 index 000000000..c2d98d2aa --- /dev/null +++ b/ui/app/components/app/connected-sites-list/index.scss @@ -0,0 +1,125 @@ +.connected-sites-list { + font-family: Roboto; + font-style: normal; + font-weight: normal; + + &__domain, &__domain--expanded { + border-bottom: 1px solid #c4c4c4; + } + + &__domain { + cursor: pointer; + } + + &__domain--expanded { + background: #FAFBFC; + cursor: initial; + } + + &__domain-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 16px; + } + + &__domain-item-info-container { + display: flex; + } + + &__identicon-container { + position: relative; + display: flex; + justify-content: center; + align-items: center; + height: 32px; + width: 32px; + } + + &__identicon-border { + height: 32px; + width: 32px; + border-radius: 50%; + border: 1px solid #F2F3F4; + position: absolute; + background: #FFFFFF; + } + + &__identicon { + width: 24px; + height: 24px; + z-index: 1; + + &--default { + z-index: 1; + color: black; + } + } + + &__domain-info { + display: flex; + flex-direction: column; + margin-left: 16px; + } + + &__domain-names { + display: flex; + align-items: center; + } + + &__domain-name { + font-size: 18px; + color: #24292E; + } + + &__domain-origin, &__domain-last-connected { + font-size: 12px; + color: #6A737D; + } + + &__domain-last-connected { + margin-top: 2px; + } + + &__expand-arrow { + align-self: flex-start; + margin-top: 6px; + } + + &__permissions { + padding-left: 16px; + padding-bottom: 24px; + } + + &__permission { + display: flex; + align-items: center; + color: #6A737D; + margin-left: 16px; + } + + &__permission-description { + margin-left: 18px; + } + + &__disconnect { + margin-top: 24px; + color: #D73A49; + cursor: pointer; + } + + &__bottom-buttons { + display: flex; + align-items: center; + } + + &__disconnect-all { + padding: 10px; + width: 50%; + } + + &__connect-to { + padding: 10px; + width: 50%; + } +} \ No newline at end of file diff --git a/ui/app/components/app/contact-list/recipient-group/recipient-group.component.js b/ui/app/components/app/contact-list/recipient-group/recipient-group.component.js index a2248326e..20e2b3494 100644 --- a/ui/app/components/app/contact-list/recipient-group/recipient-group.component.js +++ b/ui/app/components/app/contact-list/recipient-group/recipient-group.component.js @@ -15,9 +15,11 @@ export default function RecipientGroup ({ label, items, onSelect, selectedAddres return (
    - {label &&
    - {label} -
    } + {label && ( +
    + {label} +
    + )} { items.map(({ address, name }) => (
    { - event.preventDefault() - event.stopPropagation() - copyToClipboard(value) - this.debounceRestore() - }, - }, children)) -} - -Copyable.prototype.debounceRestore = function () { - this.setState({ copied: true }) - clearTimeout(this.timeout) - this.timeout = setTimeout(() => { - this.setState({ copied: false }) - }, 850) -} diff --git a/ui/app/components/app/customize-gas-modal/gas-modal-card.js b/ui/app/components/app/customize-gas-modal/gas-modal-card.js index 23754d819..5ba96dd82 100644 --- a/ui/app/components/app/customize-gas-modal/gas-modal-card.js +++ b/ui/app/components/app/customize-gas-modal/gas-modal-card.js @@ -1,8 +1,6 @@ -const Component = require('react').Component -const h = require('react-hyperscript') +import React, {Component} from 'react' const inherits = require('util').inherits const InputNumber = require('../input-number.js') -// const GasSlider = require('./gas-slider.js') module.exports = GasModalCard @@ -11,44 +9,30 @@ function GasModalCard () { Component.call(this) } -GasModalCard.prototype.render = function () { +GasModalCard.prototype.render = function GasModalCard () { const { - // memo, onChange, unitLabel, value, min, - // max, step, title, copy, } = this.props - return h('div.send-v2__gas-modal-card', [ - - h('div.send-v2__gas-modal-card__title', {}, title), - - h('div.send-v2__gas-modal-card__copy', {}, copy), - - h(InputNumber, { - unitLabel, - step, - // max, - min, - placeholder: '0', - value, - onChange, - }), - - // h(GasSlider, { - // value, - // step, - // max, - // min, - // onChange, - // }), - - ]) - + return ( +
    +
    {title}
    +
    {copy}
    + +
    + ) } diff --git a/ui/app/components/app/customize-gas-modal/gas-slider.js b/ui/app/components/app/customize-gas-modal/gas-slider.js deleted file mode 100644 index 69fd6f985..000000000 --- a/ui/app/components/app/customize-gas-modal/gas-slider.js +++ /dev/null @@ -1,50 +0,0 @@ -// const Component = require('react').Component -// const h = require('react-hyperscript') -// const inherits = require('util').inherits - -// module.exports = GasSlider - -// inherits(GasSlider, Component) -// function GasSlider () { -// Component.call(this) -// } - -// GasSlider.prototype.render = function () { -// const { -// memo, -// identities, -// onChange, -// unitLabel, -// value, -// id, -// step, -// max, -// min, -// } = this.props - -// return h('div.gas-slider', [ - -// h('input.gas-slider__input', { -// type: 'range', -// step, -// max, -// min, -// value, -// id: 'gasSlider', -// onChange: event => onChange(event.target.value), -// }, []), - -// h('div.gas-slider__bar', [ - -// h('div.gas-slider__low'), - -// h('div.gas-slider__mid'), - -// h('div.gas-slider__high'), - -// ]), - -// ]) - -// } - diff --git a/ui/app/components/app/customize-gas-modal/index.js b/ui/app/components/app/customize-gas-modal/index.js index 1f9436810..7bbe725b2 100644 --- a/ui/app/components/app/customize-gas-modal/index.js +++ b/ui/app/components/app/customize-gas-modal/index.js @@ -1,6 +1,5 @@ -const Component = require('react').Component +import React, { Component } from 'react' const PropTypes = require('prop-types') -const h = require('react-hyperscript') const inherits = require('util').inherits const connect = require('react-redux').connect const BigNumber = require('bignumber.js') @@ -328,69 +327,72 @@ CustomizeGasModal.prototype.render = function () { toNumericBase: 'dec', }) - return !gasIsLoading && h('div.send-v2__customize-gas', {}, [ - h('div.send-v2__customize-gas__content', { - }, [ - h('div.send-v2__customize-gas__header', {}, [ + if (gasIsLoading) { + return null + } - h('div.send-v2__customize-gas__title', this.context.t('customGas')), + const { t } = this.context - h('div.send-v2__customize-gas__close', { - onClick: hideModal, - }), - - ]), - - h('div.send-v2__customize-gas__body', {}, [ - - h(GasModalCard, { - value: convertedGasPrice, - min: forceGasMin || MIN_GAS_PRICE_GWEI, - step: 1, - onChange: value => this.convertAndSetGasPrice(value), - title: this.context.t('gasPrice'), - copy: this.context.t('gasPriceCalculation'), - gasIsLoading, - }), - - h(GasModalCard, { - value: convertedGasLimit, - min: 1, - step: 1, - onChange: value => this.convertAndSetGasLimit(value), - title: this.context.t('gasLimit'), - copy: this.context.t('gasLimitCalculation'), - gasIsLoading, - }), - - ]), - - h('div.send-v2__customize-gas__footer', {}, [ - - error && h('div.send-v2__customize-gas__error-message', [ - error, - ]), - - h('div.send-v2__customize-gas__revert', { - onClick: () => this.revert(), - }, [this.context.t('revert')]), - - h('div.send-v2__customize-gas__buttons', [ - h(Button, { - type: 'default', - className: 'send-v2__customize-gas__cancel', - onClick: this.props.hideModal, - }, [this.context.t('cancel')]), - h(Button, { - type: 'secondary', - className: 'send-v2__customize-gas__save', - onClick: () => !error && this.save(newGasPrice, gasLimit, gasTotal), - disabled: error, - }, [this.context.t('save')]), - ]), - - ]), - - ]), - ]) + return ( +
    +
    +
    +
    + {this.context.t('customGas')} +
    +
    +
    +
    + this.convertAndSetGasPrice(value)} + title={t('gasPrice')} + copy={t('gasPriceCalculation')} + gasIsLoading={gasIsLoading} + /> + this.convertAndSetGasLimit(value)} + title={t('gasLimit')} + copy={t('gasLimitCalculation')} + gasIsLoading={gasIsLoading} + /> +
    +
    + {error && ( +
    + {error} +
    + )} +
    this.revert()}> + {t('revert')} +
    +
    + + +
    +
    +
    +
    + ) } diff --git a/ui/app/components/app/dropdowns/account-details-dropdown.js b/ui/app/components/app/dropdowns/account-details-dropdown.js index cf2aa8ae8..dc09b8c64 100644 --- a/ui/app/components/app/dropdowns/account-details-dropdown.js +++ b/ui/app/components/app/dropdowns/account-details-dropdown.js @@ -1,10 +1,12 @@ -const Component = require('react').Component +import React, { Component } from 'react' const PropTypes = require('prop-types') -const h = require('react-hyperscript') +const { compose } = require('recompose') +const { withRouter } = require('react-router-dom') const inherits = require('util').inherits const connect = require('react-redux').connect const actions = require('../../../store/actions') const { getSelectedIdentity, getRpcPrefsForCurrentProvider } = require('../../../selectors/selectors') +const { CONNECTED_ROUTE } = require('../../../helpers/constants/routes') const genAccountLink = require('../../../../lib/account-link.js') const { Menu, Item, CloseArea } = require('./components/menu') @@ -13,7 +15,7 @@ AccountDetailsDropdown.contextTypes = { metricsEvent: PropTypes.func, } -module.exports = connect(mapStateToProps, mapDispatchToProps)(AccountDetailsDropdown) +module.exports = compose(withRouter, connect(mapStateToProps, mapDispatchToProps))(AccountDetailsDropdown) function mapStateToProps (state) { return { @@ -50,7 +52,7 @@ AccountDetailsDropdown.prototype.onClose = function (e) { this.props.onClose() } -AccountDetailsDropdown.prototype.render = function () { +AccountDetailsDropdown.prototype.render = function AccountDetailsDropdown () { const { selectedIdentity, network, @@ -59,6 +61,7 @@ AccountDetailsDropdown.prototype.render = function () { viewOnEtherscan, showRemoveAccountConfirmationModal, rpcPrefs, + history, } = this.props const address = selectedIdentity.address @@ -69,71 +72,104 @@ AccountDetailsDropdown.prototype.render = function () { const isRemovable = keyring.type !== 'HD Key Tree' - return h(Menu, { className: 'account-details-dropdown', isShowing: true }, [ - h(CloseArea, { - onClick: this.onClose, - }), - h(Item, { - onClick: (e) => { - e.stopPropagation() - this.context.metricsEvent({ - eventOpts: { - category: 'Navigation', - action: 'Account Options', - name: 'Clicked Expand View', - }, - }) - global.platform.openExtensionInBrowser() - this.props.onClose() - }, - text: this.context.t('expandView'), - icon: h(`img`, { src: 'images/expand.svg', style: { height: '15px' } }), - }), - h(Item, { - onClick: (e) => { - e.stopPropagation() - showAccountDetailModal() - this.context.metricsEvent({ - eventOpts: { - category: 'Navigation', - action: 'Account Options', - name: 'Viewed Account Details', - }, - }) - this.props.onClose() - }, - text: this.context.t('accountDetails'), - icon: h(`img`, { src: 'images/info.svg', style: { height: '15px' } }), - }), - h(Item, { - onClick: (e) => { - e.stopPropagation() - this.context.metricsEvent({ - eventOpts: { - category: 'Navigation', - action: 'Account Options', - name: 'Clicked View on Etherscan', - }, - }) - viewOnEtherscan(address, network, rpcPrefs) - this.props.onClose() - }, - text: (rpcPrefs.blockExplorerUrl - ? this.context.t('viewinExplorer') - : this.context.t('viewOnEtherscan')), - subText: (rpcPrefs.blockExplorerUrl - ? rpcPrefs.blockExplorerUrl.match(/^https?:\/\/(.+)/)[1] - : null), - icon: h(`img`, { src: 'images/open-etherscan.svg', style: { height: '15px' } }), - }), - isRemovable ? h(Item, { - onClick: (e) => { - e.stopPropagation() - showRemoveAccountConfirmationModal(selectedIdentity) - this.props.onClose() - }, - text: this.context.t('removeAccount'), - icon: h(`img`, { src: 'images/hide.svg', style: { height: '15px' } }), - }) : null, - ]) + return ( + + + { + e.stopPropagation() + this.context.metricsEvent({ + eventOpts: { + category: 'Navigation', + action: 'Account Options', + name: 'Clicked Expand View', + }, + }) + global.platform.openExtensionInBrowser() + this.props.onClose() + }} + text={this.context.t('expandView')} + icon={( + + )} + /> + { + e.stopPropagation() + showAccountDetailModal() + this.context.metricsEvent({ + eventOpts: { + category: 'Navigation', + action: 'Account Options', + name: 'Viewed Account Details', + }, + }) + this.props.onClose() + }} + text={this.context.t('accountDetails')} + icon={( + + )} + /> + { + e.stopPropagation() + this.context.metricsEvent({ + eventOpts: { + category: 'Navigation', + action: 'Account Options', + name: 'Clicked View on Etherscan', + }, + }) + viewOnEtherscan(address, network, rpcPrefs) + this.props.onClose() + }} + text={ + rpcPrefs.blockExplorerUrl + ? this.context.t('viewinExplorer') + : this.context.t('viewOnEtherscan') + } + subText={ + rpcPrefs.blockExplorerUrl + ? rpcPrefs.blockExplorerUrl.match(/^https?:\/\/(.+)/)[1] + : null + } + icon={( + + )} + /> + { + e.stopPropagation() + this.context.metricsEvent({ + eventOpts: { + category: 'Navigation', + action: 'Account Options', + name: 'Opened Connected Sites', + }, + }) + history.push(CONNECTED_ROUTE) + }} + text={this.context.t('connectedSites')} + icon={( + + )} + /> + { + isRemovable + ? ( + { + e.stopPropagation() + showRemoveAccountConfirmationModal(selectedIdentity) + this.props.onClose() + }} + text={this.context.t('removeAccount')} + icon={} + /> + ) + : null + } + + ) } diff --git a/ui/app/components/app/dropdowns/components/dropdown.js b/ui/app/components/app/dropdowns/components/dropdown.js index fd7055803..ad6c1c514 100644 --- a/ui/app/components/app/dropdowns/components/dropdown.js +++ b/ui/app/components/app/dropdowns/components/dropdown.js @@ -1,6 +1,5 @@ -const Component = require('react').Component +import React, { Component } from 'react' const PropTypes = require('prop-types') -const h = require('react-hyperscript') const MenuDroppo = require('../../menu-droppo') const extend = require('xtend') @@ -23,31 +22,28 @@ class Dropdown extends Component { boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px', }, innerStyle) - return h( - MenuDroppo, - { - containerClassName, - useCssTransition, - isOpen, - zIndex: 55, - onClickOutside, - style, - innerStyle: innerStyleDefaults, - }, - [ - h( - 'style', - ` - li.dropdown-menu-item:hover { - color:rgb(225, 225, 225); - background-color: rgba(255, 255, 255, 0.05); - border-radius: 4px; - } - li.dropdown-menu-item { color: rgb(185, 185, 185); } - ` - ), - ...children, - ] + return ( + + + { children } + ) } } @@ -70,14 +66,14 @@ class DropdownMenuItem extends Component { render () { const { onClick, closeMenu, children, style } = this.props - return h( - 'li.dropdown-menu-item', - { - onClick: () => { + return ( +
  • { onClick() closeMenu() - }, - style: Object.assign({ + }} + style={Object.assign({ listStyle: 'none', padding: '8px 0px', fontSize: '18px', @@ -87,9 +83,10 @@ class DropdownMenuItem extends Component { justifyContent: 'flex-start', alignItems: 'center', color: 'white', - }, style), - }, - children + }, style)} + > + {children} +
  • ) } } diff --git a/ui/app/components/app/dropdowns/components/menu.js b/ui/app/components/app/dropdowns/components/menu.js index 63501eaa9..669a50c63 100644 --- a/ui/app/components/app/dropdowns/components/menu.js +++ b/ui/app/components/app/dropdowns/components/menu.js @@ -1,53 +1,85 @@ -const inherits = require('util').inherits -const Component = require('react').Component -const h = require('react-hyperscript') +import PropTypes from 'prop-types' +import React from 'react' +import classnames from 'classnames' -inherits(Menu, Component) -function Menu () { Component.call(this) } - -Menu.prototype.render = function () { - const { className = '', children, isShowing } = this.props +/** + * Menu component + * @return {Component|null} + */ +function Menu (props) { + const { className, children, isShowing } = props return isShowing - ? h('div', { className: `menu ${className}` }, children) - : h('noscript') + ?
    {children}
    + : null } -inherits(Item, Component) -function Item () { Component.call(this) } +Menu.defaultProps = { + className: '', + isShowing: false, + children: null, +} -Item.prototype.render = function () { +Menu.propTypes = { + className: PropTypes.string, + children: PropTypes.node, + isShowing: PropTypes.bool, +} + +function Item (props) { const { icon, children, text, subText, - className = '', + className, onClick, - } = this.props - const itemClassName = `menu__item ${className} ${onClick ? 'menu__item--clickable' : ''}` - const iconComponent = icon ? h('div.menu__item__icon', [icon]) : null - const textComponent = text ? h('div.menu__item__text', text) : null - const subTextComponent = subText ? h('div.menu__item__subtext', subText) : null + } = props + const itemClassName = classnames('menu__item', className, { + 'menu__item--clickable': Boolean(onClick), + }) return children - ? h('div', { className: itemClassName, onClick }, children) - : h('div.menu__item', { className: itemClassName, onClick }, [ iconComponent, textComponent, subTextComponent ] - .filter(d => Boolean(d)) + ?
    {children}
    + : ( +
    + {icon ?
    {icon}
    : null} + {text ?
    {text}
    : null} + {subText ?
    {subText}
    : null} +
    ) } -inherits(Divider, Component) -function Divider () { Component.call(this) } - -Divider.prototype.render = function () { - return h('div.menu__divider') +Item.defaultProps = { + children: null, + icon: null, + text: null, + subText: null, + className: '', + onClick: null, } -inherits(CloseArea, Component) -function CloseArea () { Component.call(this) } +Item.propTypes = { + icon: PropTypes.node, + children: PropTypes.node, + text: PropTypes.node, + subText: PropTypes.node, + className: PropTypes.string, + onClick: PropTypes.func, +} -CloseArea.prototype.render = function () { - return h('div.menu__close-area', { onClick: this.props.onClick }) +function Divider () { + return
    +} + +function CloseArea ({ onClick }) { + return
    +} + +CloseArea.propTypes = { + onClick: PropTypes.func.isRequired, } module.exports = { Menu, Item, Divider, CloseArea } diff --git a/ui/app/components/app/dropdowns/components/network-dropdown-icon.js b/ui/app/components/app/dropdowns/components/network-dropdown-icon.js index d4a2c2ff7..ca318c9e5 100644 --- a/ui/app/components/app/dropdowns/components/network-dropdown-icon.js +++ b/ui/app/components/app/dropdowns/components/network-dropdown-icon.js @@ -1,47 +1,56 @@ -const inherits = require('util').inherits -const Component = require('react').Component -const h = require('react-hyperscript') +import PropTypes from 'prop-types' +import React from 'react' - -inherits(NetworkDropdownIcon, Component) -module.exports = NetworkDropdownIcon - -function NetworkDropdownIcon () { - Component.call(this) -} - -NetworkDropdownIcon.prototype.render = function () { +function NetworkDropdownIcon (props) { const { backgroundColor, isSelected, - innerBorder = 'none', - diameter = '12', + innerBorder, + diameter, loading, - } = this.props + } = props return loading - ? h('span.pointer.network-indicator', { - style: { - display: 'flex', - alignItems: 'center', - flexDirection: 'row', - }, - }, [ - h('img', { - style: { - width: '27px', - }, - src: 'images/loading.svg', - }), - ]) - : h(`.menu-icon-circle${isSelected ? '--active' : ''}`, {}, - h('div', { - style: { - background: backgroundColor, - border: innerBorder, - height: `${diameter}px`, - width: `${diameter}px`, - }, - }) + ? ( + + + + ) + : ( +
    +
    +
    ) } + +NetworkDropdownIcon.defaultProps = { + backgroundColor: undefined, + loading: false, + innerBorder: 'none', + diameter: '12', + isSelected: false, +} + +NetworkDropdownIcon.propTypes = { + backgroundColor: PropTypes.string, + loading: PropTypes.bool, + innerBorder: PropTypes.string, + diameter: PropTypes.string, + isSelected: PropTypes.bool, +} + +module.exports = NetworkDropdownIcon diff --git a/ui/app/components/app/dropdowns/network-dropdown.js b/ui/app/components/app/dropdowns/network-dropdown.js index e6a24ef11..cf56919c4 100644 --- a/ui/app/components/app/dropdowns/network-dropdown.js +++ b/ui/app/components/app/dropdowns/network-dropdown.js @@ -1,6 +1,5 @@ -const Component = require('react').Component -const PropTypes = require('prop-types') -const h = require('react-hyperscript') +import PropTypes from 'prop-types' +import React, {Component} from 'react' const inherits = require('util').inherits const connect = require('react-redux').connect const { withRouter } = require('react-router-dom') @@ -67,10 +66,9 @@ module.exports = compose( // TODO: specify default props and proptypes -NetworkDropdown.prototype.render = function () { - const props = this.props - const { provider: { type: providerType, rpcTarget: activeNetwork }, setNetworksTabAddMode } = props - const rpcListDetail = props.frequentRpcListDetail +NetworkDropdown.prototype.render = function NetworkDropdown () { + const { provider: { type: providerType, rpcTarget: activeNetwork }, setNetworksTabAddMode } = this.props + const rpcListDetail = this.props.frequentRpcListDetail const isOpen = this.props.networkDropdownOpen const dropdownMenuItemStyle = { fontSize: '16px', @@ -78,201 +76,206 @@ NetworkDropdown.prototype.render = function () { padding: '12px 0', } - return h(Dropdown, { - isOpen, - onClickOutside: (event) => { - const { classList } = event.target - const isInClassList = className => classList.contains(className) - const notToggleElementIndex = R.findIndex(isInClassList)(notToggleElementClassnames) + return ( + { + const { classList } = event.target + const isInClassList = className => classList.contains(className) + const notToggleElementIndex = R.findIndex(isInClassList)(notToggleElementClassnames) - if (notToggleElementIndex === -1) { - this.props.hideNetworkDropdown() - } - }, - containerClassName: 'network-droppo', - zIndex: 55, - style: { - position: 'absolute', - top: '58px', - width: '309px', - zIndex: '55px', - }, - innerStyle: { - padding: '18px 8px', - }, - }, [ - - h('div.network-dropdown-header', {}, [ - h('div.network-dropdown-title', {}, this.context.t('networks')), - - h('div.network-dropdown-divider'), - - h('div.network-dropdown-content', - {}, - this.context.t('defaultNetwork') - ), - ]), - - h( - DropdownMenuItem, - { - key: 'main', - closeMenu: () => this.props.hideNetworkDropdown(), - onClick: () => this.handleClick('mainnet'), - style: { ...dropdownMenuItemStyle, borderColor: '#038789' }, - }, - [ - providerType === 'mainnet' ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'), - h(NetworkDropdownIcon, { - backgroundColor: '#29B6AF', // $java - isSelected: providerType === 'mainnet', - }), - h('span.network-name-item', { - style: { - color: providerType === 'mainnet' ? '#ffffff' : '#9b9b9b', - }, - }, this.context.t('mainnet')), - ] - ), - - h( - DropdownMenuItem, - { - key: 'ropsten', - closeMenu: () => this.props.hideNetworkDropdown(), - onClick: () => this.handleClick('ropsten'), - style: dropdownMenuItemStyle, - }, - [ - providerType === 'ropsten' ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'), - h(NetworkDropdownIcon, { - backgroundColor: '#ff4a8d', // $wild-strawberry - isSelected: providerType === 'ropsten', - }), - h('span.network-name-item', { - style: { - color: providerType === 'ropsten' ? '#ffffff' : '#9b9b9b', - }, - }, this.context.t('ropsten')), - ] - ), - - h( - DropdownMenuItem, - { - key: 'kovan', - closeMenu: () => this.props.hideNetworkDropdown(), - onClick: () => this.handleClick('kovan'), - style: dropdownMenuItemStyle, - }, - [ - providerType === 'kovan' ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'), - h(NetworkDropdownIcon, { - backgroundColor: '#7057ff', // $cornflower-blue - isSelected: providerType === 'kovan', - }), - h('span.network-name-item', { - style: { - color: providerType === 'kovan' ? '#ffffff' : '#9b9b9b', - }, - }, this.context.t('kovan')), - ] - ), - - h( - DropdownMenuItem, - { - key: 'rinkeby', - closeMenu: () => this.props.hideNetworkDropdown(), - onClick: () => this.handleClick('rinkeby'), - style: dropdownMenuItemStyle, - }, - [ - providerType === 'rinkeby' ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'), - h(NetworkDropdownIcon, { - backgroundColor: '#f6c343', // $saffron - isSelected: providerType === 'rinkeby', - }), - h('span.network-name-item', { - style: { - color: providerType === 'rinkeby' ? '#ffffff' : '#9b9b9b', - }, - }, this.context.t('rinkeby')), - ] - ), - - 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'), - style: dropdownMenuItemStyle, - }, - [ - providerType === 'localhost' ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'), - h(NetworkDropdownIcon, { - isSelected: providerType === 'localhost', - innerBorder: '1px solid #9b9b9b', - }), - h('span.network-name-item', { - style: { - color: providerType === 'localhost' ? '#ffffff' : '#9b9b9b', - }, - }, this.context.t('localhost')), - ] - ), - - this.renderCustomOption(props.provider), - this.renderCommonRpc(rpcListDetail, props.provider), - - h( - DropdownMenuItem, - { - closeMenu: () => this.props.hideNetworkDropdown(), - onClick: () => { + if (notToggleElementIndex === -1) { + this.props.hideNetworkDropdown() + } + }} + containerClassName="network-droppo" + zIndex={55} + style={{ + position: 'absolute', + top: '58px', + width: '309px', + zIndex: '55px', + }} + innerStyle={{ + padding: '18px 8px', + }} + > +
    +
    + {this.context.t('networks')} +
    +
    +
    + {this.context.t('defaultNetwork')} +
    +
    + this.props.hideNetworkDropdown()} + onClick={() => this.handleClick('mainnet')} + style={{ ...dropdownMenuItemStyle, borderColor: '#038789' }} + > + { + providerType === 'mainnet' + ? + :
    + } + + + {this.context.t('mainnet')} + +
    + this.props.hideNetworkDropdown()} + onClick={() => this.handleClick('ropsten')} + style={dropdownMenuItemStyle} + > + { + providerType === 'ropsten' + ? + :
    + } + + + {this.context.t('ropsten')} + +
    + this.props.hideNetworkDropdown()} + onClick={() => this.handleClick('kovan')} + style={dropdownMenuItemStyle} + > + { + providerType === 'kovan' + ? + :
    + } + + + {this.context.t('kovan')} + +
    + this.props.hideNetworkDropdown()} + onClick={() => this.handleClick('rinkeby')} + style={dropdownMenuItemStyle} + > + { + providerType === 'rinkeby' + ? + :
    + } + + + {this.context.t('rinkeby')} + +
    + this.props.hideNetworkDropdown()} + onClick={() => this.handleClick('goerli')} + style={dropdownMenuItemStyle} + > + { + providerType === 'goerli' + ? + :
    + } + + + {this.context.t('goerli')} + +
    + this.props.hideNetworkDropdown()} + onClick={() => this.handleClick('localhost')} + style={dropdownMenuItemStyle} + > + { + providerType === 'localhost' + ? + :
    + } + + + {this.context.t('localhost')} + +
    + {this.renderCustomOption(this.props.provider)} + {this.renderCommonRpc(rpcListDetail, this.props.provider)} + this.props.hideNetworkDropdown()} + onClick={() => { setNetworksTabAddMode(true) this.props.history.push(NETWORKS_ROUTE) - }, - style: dropdownMenuItemStyle, - }, - [ - activeNetwork === 'custom' ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'), - h(NetworkDropdownIcon, { - isSelected: activeNetwork === 'custom', - innerBorder: '1px solid #9b9b9b', - }), - h('span.network-name-item', { - style: { - color: activeNetwork === 'custom' ? '#ffffff' : '#9b9b9b', - }, - }, this.context.t('customRPC')), - ] - ), - - ]) + }} + style={dropdownMenuItemStyle} + > + { + activeNetwork === 'custom' + ? + :
    + } + + + {this.context.t('customRPC')} + +
    + + ) } NetworkDropdown.prototype.handleClick = function (newProviderType) { @@ -332,45 +335,56 @@ NetworkDropdown.prototype.renderCommonRpc = function (rpcListDetail, provider) { return null } else { const chainId = entry.chainId - return h( - DropdownMenuItem, - { - key: `common${rpc}`, - closeMenu: () => this.props.hideNetworkDropdown(), - onClick: () => props.setRpcTarget(rpc, chainId, ticker, nickname), - style: { + return ( + this.props.hideNetworkDropdown()} + onClick={() => props.setRpcTarget(rpc, chainId, ticker, nickname)} + style={{ fontSize: '16px', lineHeight: '20px', padding: '12px 0', - }, - }, - [ - currentRpcTarget ? h('i.fa.fa-check') : h('.network-check__transparent', '✓'), - h('i.fa.fa-question-circle.fa-med.menu-icon-circle'), - h('span.network-name-item', { - style: { - color: currentRpcTarget ? '#ffffff' : '#9b9b9b', - }, - }, nickname || rpc), - h('i.fa.fa-times.delete', - { - onClick: (e) => { - e.stopPropagation() - props.delRpcTarget(rpc) - }, - }), - ] + }} + > + { + currentRpcTarget + ? + :
    + } + + + {nickname || rpc} + + { + e.stopPropagation() + props.delRpcTarget(rpc) + }} + /> +
    ) } }) } -NetworkDropdown.prototype.renderCustomOption = function (provider) { +/** + * @return {Component|null} + */ +NetworkDropdown.prototype.renderCustomOption = function NetworkDropdown (provider) { const { rpcTarget, type, ticker, nickname } = provider - const props = this.props - const network = props.network + const network = this.props.network - if (type !== 'rpc') return null + if (type !== 'rpc') { + return null + } switch (rpcTarget) { @@ -378,27 +392,28 @@ NetworkDropdown.prototype.renderCustomOption = function (provider) { return null default: - return h( - DropdownMenuItem, - { - key: rpcTarget, - onClick: () => props.setRpcTarget(rpcTarget, network, ticker, nickname), - closeMenu: () => this.props.hideNetworkDropdown(), - style: { + return ( + this.props.setRpcTarget(rpcTarget, network, ticker, nickname)} + closeMenu={() => this.props.hideNetworkDropdown()} + style={{ fontSize: '16px', lineHeight: '20px', padding: '12px 0', - }, - }, - [ - h('i.fa.fa-check'), - h('i.fa.fa-question-circle.fa-med.menu-icon-circle'), - h('span.network-name-item', { - style: { + }} + > + + + + {nickname || rpcTarget} + + ) } } diff --git a/ui/app/components/app/dropdowns/simple-dropdown.js b/ui/app/components/app/dropdowns/simple-dropdown.js index bba088ed1..081674397 100644 --- a/ui/app/components/app/dropdowns/simple-dropdown.js +++ b/ui/app/components/app/dropdowns/simple-dropdown.js @@ -1,15 +1,18 @@ -const { Component } = require('react') -const PropTypes = require('prop-types') -const h = require('react-hyperscript') -const classnames = require('classnames') -const R = require('ramda') +import classnames from 'classnames' +import PropTypes from 'prop-types' +import React, { Component } from 'react' +import R from 'ramda' class SimpleDropdown extends Component { - constructor (props) { - super(props) - this.state = { - isOpen: false, - } + static propTypes = { + options: PropTypes.array.isRequired, + placeholder: PropTypes.string, + onSelect: PropTypes.func, + selectedOption: PropTypes.string, + } + + state = { + isOpen: false, } getDisplayValue () { @@ -26,67 +29,58 @@ class SimpleDropdown extends Component { } toggleOpen () { - const { isOpen } = this.state - this.setState({ isOpen: !isOpen }) + this.setState((prevState) => ({ + isOpen: !prevState.isOpen, + })) } renderOptions () { const { options, onSelect, selectedOption } = this.props - return h('div', [ - h('div.simple-dropdown__close-area', { - onClick: event => { - event.stopPropagation() - this.handleClose() - }, - }), - h('div.simple-dropdown__options', [ - ...options.map(option => { - return h( - 'div.simple-dropdown__option', - { - className: classnames({ + return ( +
    +
    { + event.stopPropagation() + this.handleClose() + }} + /> +
    + {options.map((option) => ( +
    { + })} + key={option.value} + onClick={() => { if (option.value !== selectedOption) { onSelect(option.value) } this.handleClose() - }, - }, - option.displayValue || option.value, - ) - }), - ]), - ]) + }} + > + {option.displayValue || option.value} +
    + ))} +
    +
    + ) } render () { const { placeholder } = this.props const { isOpen } = this.state - return h( - 'div.simple-dropdown', - { - onClick: () => this.toggleOpen(), - }, - [ - h('div.simple-dropdown__selected', this.getDisplayValue() || placeholder || 'Select'), - h('i.fa.fa-caret-down.fa-lg.simple-dropdown__caret'), - isOpen && this.renderOptions(), - ] + return ( +
    this.toggleOpen()}> +
    {this.getDisplayValue() || placeholder || 'Select'}
    + + {isOpen && this.renderOptions()} +
    ) } } -SimpleDropdown.propTypes = { - options: PropTypes.array.isRequired, - placeholder: PropTypes.string, - onSelect: PropTypes.func, - selectedOption: PropTypes.string, -} - module.exports = SimpleDropdown diff --git a/ui/app/components/app/dropdowns/tests/menu.test.js b/ui/app/components/app/dropdowns/tests/menu.test.js index 6413c0c2c..b3bc34fa9 100644 --- a/ui/app/components/app/dropdowns/tests/menu.test.js +++ b/ui/app/components/app/dropdowns/tests/menu.test.js @@ -11,7 +11,7 @@ describe('Dropdown Menu Components', () => { beforeEach(() => { wrapper = shallow( - + ) }) @@ -31,14 +31,14 @@ describe('Dropdown Menu Components', () => { ) }) it('add className based on props', () => { - assert.equal(wrapper.find('.menu__item').prop('className'), 'menu__item menu__item test className menu__item--clickable') + assert.equal(wrapper.find('.menu__item').prop('className'), 'menu__item test foo1 menu__item--clickable') }) it('simulates onClick called', () => { @@ -73,9 +73,11 @@ describe('Dropdown Menu Components', () => { const onClickSpy = sinon.spy() beforeEach(() => { - wrapper = shallow() + wrapper = shallow(( + + )) }) it('simulates click', () => { diff --git a/ui/app/components/app/dropdowns/tests/network-dropdown-icon.test.js b/ui/app/components/app/dropdowns/tests/network-dropdown-icon.test.js index ed7b7e253..ce3853a63 100644 --- a/ui/app/components/app/dropdowns/tests/network-dropdown-icon.test.js +++ b/ui/app/components/app/dropdowns/tests/network-dropdown-icon.test.js @@ -7,12 +7,14 @@ describe('Network Dropdown Icon', () => { let wrapper beforeEach(() => { - wrapper = shallow() + wrapper = shallow(( + + )) }) it('adds style props based on props', () => { 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 4a81b973f..98cba1d2e 100644 --- a/ui/app/components/app/dropdowns/tests/network-dropdown.test.js +++ b/ui/app/components/app/dropdowns/tests/network-dropdown.test.js @@ -57,7 +57,7 @@ describe('Network Dropdown', () => { beforeEach(() => { wrapper = mountWithRouter( - , + , ) }) diff --git a/ui/app/components/app/dropdowns/token-menu-dropdown.js b/ui/app/components/app/dropdowns/token-menu-dropdown.js index e2730aea2..65c559f70 100644 --- a/ui/app/components/app/dropdowns/token-menu-dropdown.js +++ b/ui/app/components/app/dropdowns/token-menu-dropdown.js @@ -1,6 +1,5 @@ -const Component = require('react').Component -const PropTypes = require('prop-types') -const h = require('react-hyperscript') +import PropTypes from 'prop-types' +import React, { Component } from 'react' const inherits = require('util').inherits const connect = require('react-redux').connect const actions = require('../../../store/actions') @@ -40,29 +39,29 @@ TokenMenuDropdown.prototype.onClose = function (e) { this.props.onClose() } -TokenMenuDropdown.prototype.render = function () { +TokenMenuDropdown.prototype.render = function TokenMenuDropdown () { const { showHideTokenConfirmationModal } = this.props - return h(Menu, { className: 'token-menu-dropdown', isShowing: true }, [ - h(CloseArea, { - onClick: this.onClose, - }), - h(Item, { - onClick: (e) => { - e.stopPropagation() - showHideTokenConfirmationModal(this.props.token) - this.props.onClose() - }, - text: this.context.t('hideToken'), - }), - h(Item, { - onClick: (e) => { - e.stopPropagation() - const url = genAccountLink(this.props.token.address, this.props.network) - global.platform.openWindow({ url }) - this.props.onClose() - }, - text: this.context.t('viewOnEtherscan'), - }), - ]) + return ( + + + { + e.stopPropagation() + showHideTokenConfirmationModal(this.props.token) + this.props.onClose() + }} + text={this.context.t('hideToken')} + /> + { + e.stopPropagation() + const url = genAccountLink(this.props.token.address, this.props.network) + global.platform.openWindow({ url }) + this.props.onClose() + }} + text={this.context.t('viewOnEtherscan')} + /> + + ) } 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 7fb5aa6f4..b881db6e5 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 @@ -119,10 +119,12 @@ export default class AdvancedGasInputs extends Component { value={value} onChange={onChange} /> -
    +
    onChange({ target: { value: value + 1 } })} @@ -159,21 +161,21 @@ export default class AdvancedGasInputs extends Component { errorText: gasPriceErrorText, errorType: gasPriceErrorType, } = this.gasPriceError({ insufficientBalance, customPriceIsSafe, isSpeedUp, gasPrice }) - const gasPriceErrorComponent = gasPriceErrorType ? + const gasPriceErrorComponent = gasPriceErrorType ? (
    { gasPriceErrorText } -
    : - null +
    + ) : null const { errorText: gasLimitErrorText, errorType: gasLimitErrorType, } = this.gasLimitError({ insufficientBalance, gasLimit }) - const gasLimitErrorComponent = gasLimitErrorType ? + const gasLimitErrorComponent = gasLimitErrorType ? (
    { gasLimitErrorText } -
    : - null +
    + ) : null return (
    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 306dd03a0..19e3aa776 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 @@ -83,17 +83,19 @@ export default class AdvancedTabContent extends Component { />
    { isEthereumNetwork - ?
    -
    { t('liveGasPricePredictions') }
    - {!gasEstimatesLoading - ? - : - } -
    - { t('slower') } - { t('faster') } + ? ( +
    +
    { t('liveGasPricePredictions') }
    + {!gasEstimatesLoading + ? + : + } +
    + { t('slower') } + { t('faster') } +
    -
    + ) :
    { t('chartOnlyAvailableEth') }
    }
    diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/tests/advanced-tab-content-component.test.js b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/tests/advanced-tab-content-component.test.js index 05075f3ba..16b15df41 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/tests/advanced-tab-content-component.test.js +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/advanced-tab-content/tests/advanced-tab-content-component.test.js @@ -18,18 +18,20 @@ describe('AdvancedTabContent Component', function () { let wrapper beforeEach(() => { - wrapper = shallow() + wrapper = shallow(( + + )) }) afterEach(() => { diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/basic-tab-content.component.js b/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/basic-tab-content.component.js index c804abe3a..94d22ee4a 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/basic-tab-content.component.js +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/basic-tab-content.component.js @@ -21,11 +21,13 @@ export default class BasicTabContent extends Component {
    { t('estimatedProcessingTimes') }
    { t('selectAHigherGasFee') }
    {!gasPriceButtonGroupProps.loading - ? + ? ( + + ) : }
    { t('acceleratingATransaction') }
    diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/tests/basic-tab-content-component.test.js b/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/tests/basic-tab-content-component.test.js index 0989ac677..80fbffe05 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/tests/basic-tab-content-component.test.js +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/basic-tab-content/tests/basic-tab-content-component.test.js @@ -38,9 +38,11 @@ describe('BasicTabContent Component', function () { let wrapper beforeEach(() => { - wrapper = shallow() + wrapper = shallow(( + + )) }) describe('render', () => { 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 f405cb7b9..daf91eae3 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 @@ -160,13 +160,14 @@ export default class GasModalPageContainer extends Component { return ( - {tabsToRender.map(({ name, content }, i) => -
    - { content } - { this.renderInfoRows(newTotalFiat, newTotalEth, sendAmount, transactionFee) } -
    -
    - )} + {tabsToRender.map(({ name, content }, i) => ( + +
    + { content } + { this.renderInfoRows(newTotalFiat, newTotalEth, sendAmount, transactionFee) } +
    +
    + ))}
    ) } diff --git a/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-component.test.js b/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-component.test.js index 3e416db49..50efb46f0 100644 --- a/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-component.test.js +++ b/ui/app/components/app/gas-customization/gas-modal-page-container/tests/gas-modal-page-container-component.test.js @@ -63,23 +63,25 @@ describe('GasModalPageContainer Component', function () { let wrapper beforeEach(() => { - wrapper = shallow( 'mockupdateCustomGasPrice'} - updateCustomGasLimit={() => 'mockupdateCustomGasLimit'} - customGasPrice={21} - customGasLimit={54321} - gasPriceButtonGroupProps={mockGasPriceButtonGroupProps} - infoRowProps={mockInfoRowProps} - currentTimeEstimate="1 min 31 sec" - customGasPriceInHex="mockCustomGasPriceInHex" - customGasLimitInHex="mockCustomGasLimitInHex" - insufficientBalance={false} - disableSave={false} - />) + wrapper = shallow(( + 'mockupdateCustomGasPrice'} + updateCustomGasLimit={() => 'mockupdateCustomGasLimit'} + customGasPrice={21} + customGasLimit={54321} + gasPriceButtonGroupProps={mockGasPriceButtonGroupProps} + infoRowProps={mockInfoRowProps} + currentTimeEstimate="1 min 31 sec" + customGasPriceInHex="mockCustomGasPriceInHex" + customGasLimitInHex="mockCustomGasLimitInHex" + insufficientBalance={false} + disableSave={false} + /> + )) }) afterEach(() => { @@ -134,10 +136,12 @@ describe('GasModalPageContainer Component', function () { it('should pass the correct renderTabs property to PageContainer', () => { sinon.stub(GP, 'renderTabs').returns('mockTabs') - const renderTabsWrapperTester = shallow(, { context: { t: (str1, str2) => str2 ? str1 + str2 : str1 } }) + const renderTabsWrapperTester = shallow(( + + ), { context: { t: (str1, str2) => str2 ? str1 + str2 : str1 } }) const { tabsComponent } = renderTabsWrapperTester.find(PageContainer).props() assert.equal(tabsComponent, 'mockTabs') GasModalPageContainer.prototype.renderTabs.restore() @@ -184,24 +188,26 @@ describe('GasModalPageContainer Component', function () { }) it('should not render the basic tab if hideBasic is true', () => { - wrapper = shallow( 'mockupdateCustomGasPrice'} - updateCustomGasLimit={() => 'mockupdateCustomGasLimit'} - customGasPrice={21} - customGasLimit={54321} - gasPriceButtonGroupProps={mockGasPriceButtonGroupProps} - infoRowProps={mockInfoRowProps} - currentTimeEstimate="1 min 31 sec" - customGasPriceInHex="mockCustomGasPriceInHex" - customGasLimitInHex="mockCustomGasLimitInHex" - insufficientBalance={false} - disableSave={false} - hideBasic - />) + wrapper = shallow(( + 'mockupdateCustomGasPrice'} + updateCustomGasLimit={() => 'mockupdateCustomGasLimit'} + customGasPrice={21} + customGasLimit={54321} + gasPriceButtonGroupProps={mockGasPriceButtonGroupProps} + infoRowProps={mockInfoRowProps} + currentTimeEstimate="1 min 31 sec" + customGasPriceInHex="mockCustomGasPriceInHex" + customGasLimitInHex="mockCustomGasLimitInHex" + insufficientBalance={false} + disableSave={false} + hideBasic + /> + )) const renderTabsResult = wrapper.instance().renderTabs() const renderedTabs = shallow(renderTabsResult) 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 0412b3381..b578ff1ed 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,13 +49,15 @@ export default class GasPriceButtonGroup extends Component { className, showCheck, }) { - return (
    - { gasEstimateType &&
    { this.gasEstimateTypeLabel(gasEstimateType) }
    } - { timeEstimate &&
    { timeEstimate }
    } - { feeInPrimaryCurrency &&
    { feeInPrimaryCurrency }
    } - { feeInSecondaryCurrency &&
    { feeInSecondaryCurrency }
    } - { showCheck &&
    } -
    ) + return ( +
    + { gasEstimateType &&
    { this.gasEstimateTypeLabel(gasEstimateType) }
    } + { timeEstimate &&
    { timeEstimate }
    } + { feeInPrimaryCurrency &&
    { feeInPrimaryCurrency }
    } + { feeInSecondaryCurrency &&
    { feeInSecondaryCurrency }
    } + { showCheck &&
    } +
    + ) } renderButton ({ @@ -88,14 +90,16 @@ export default class GasPriceButtonGroup extends Component { return ( !buttonDataLoading - ? - { gasButtonInfo.map((obj, index) => this.renderButton(obj, buttonPropsAndFlags, index)) } - + ? ( + + { gasButtonInfo.map((obj, index) => this.renderButton(obj, buttonPropsAndFlags, index)) } + + ) :
    { this.context.t('loading') }
    ) } diff --git a/ui/app/components/app/gas-customization/gas-price-button-group/tests/gas-price-button-group-component.test.js b/ui/app/components/app/gas-customization/gas-price-button-group/tests/gas-price-button-group-component.test.js index 85f53d08e..1d924f850 100644 --- a/ui/app/components/app/gas-customization/gas-price-button-group/tests/gas-price-button-group-component.test.js +++ b/ui/app/components/app/gas-customization/gas-price-button-group/tests/gas-price-button-group-component.test.js @@ -48,9 +48,11 @@ describe('GasPriceButtonGroup Component', function () { let wrapper beforeEach(() => { - wrapper = shallow() + wrapper = shallow(( + + )) }) afterEach(() => { 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 419cae0cd..452544abe 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 @@ -214,7 +214,9 @@ export function generateChart (gasPrices, estimatedTimes, gasPricesMax, estimate tick: { values: [Math.floor(gasPrices[0]), Math.ceil(gasPricesMax)], outer: false, - format: function (val) { return val + ' GWEI' }, + format: function (val) { + return val + ' GWEI' + }, }, padding: {left: gasPricesMax / 50, right: gasPricesMax / 50}, label: { diff --git a/ui/app/components/app/gas-customization/gas-slider/gas-slider.component.js b/ui/app/components/app/gas-customization/gas-slider/gas-slider.component.js index 8233dedb5..629d81f0e 100644 --- a/ui/app/components/app/gas-customization/gas-slider/gas-slider.component.js +++ b/ui/app/components/app/gas-customization/gas-slider/gas-slider.component.js @@ -36,7 +36,7 @@ export default class GasSlider extends Component { onChange={event => onChange(event.target.value)} />
    -
    +
    {lowLabel} diff --git a/ui/app/components/app/index.scss b/ui/app/components/app/index.scss index 1afbebd00..7578aa204 100644 --- a/ui/app/components/app/index.scss +++ b/ui/app/components/app/index.scss @@ -38,7 +38,7 @@ @import '../../pages/index'; -@import 'provider-page-container/index'; +@import 'permission-page-container/index'; @import 'selected-account/index'; @@ -64,18 +64,12 @@ @import 'transaction-status/index'; -@import 'app-header/index'; - @import 'sidebars/index'; @import '../ui/unit-input/index'; @import 'gas-customization/gas-modal-page-container/index'; -@import 'gas-customization/gas-modal-page-container/index'; - -@import 'gas-customization/gas-modal-page-container/index'; - @import 'gas-customization/index'; @import 'gas-customization/gas-price-button-group/index'; @@ -87,3 +81,7 @@ @import 'multiple-notifications/index'; @import 'signature-request/index'; + +@import 'connected-sites-list/index'; + +@import '../ui/icon-with-fallback/index'; diff --git a/ui/app/components/app/input-number.js b/ui/app/components/app/input-number.js index 8a6ec725c..7506808b3 100644 --- a/ui/app/components/app/input-number.js +++ b/ui/app/components/app/input-number.js @@ -1,5 +1,5 @@ +import React from 'react' const Component = require('react').Component -const h = require('react-hyperscript') const inherits = require('util').inherits const { addCurrencies, @@ -28,7 +28,9 @@ function removeLeadingZeroes (str) { InputNumber.prototype.setValue = function (newValue) { newValue = removeLeadingZeroes(newValue) - if (newValue && !isValidInput(newValue)) return + if (newValue && !isValidInput(newValue)) { + return + } const { fixed, min = -1, max = Infinity, onChange } = this.props newValue = fixed ? newValue.toFixed(4) : newValue @@ -50,32 +52,36 @@ InputNumber.prototype.setValue = function (newValue) { } } -InputNumber.prototype.render = function () { +InputNumber.prototype.render = function InputNumber () { const { unitLabel, step = 1, placeholder, value } = this.props - return h('div.customize-gas-input-wrapper', {}, [ - h('input', { - className: 'customize-gas-input', - value, - placeholder, - type: 'number', - onChange: e => { - this.setValue(e.target.value) - }, - min: 0, - }), - h('span.gas-tooltip-input-detail', {}, [unitLabel]), - h('div.gas-tooltip-input-arrows', {}, [ - h('div.gas-tooltip-input-arrow-wrapper', { - onClick: () => this.setValue(addCurrencies(value, step, { toNumericBase: 'dec' })), - }, [ - h('i.fa.fa-angle-up'), - ]), - h('div.gas-tooltip-input-arrow-wrapper', { - onClick: () => this.setValue(subtractCurrencies(value, step, { toNumericBase: 'dec' })), - }, [ - h('i.fa.fa-angle-down'), - ]), - ]), - ]) + return ( +
    + { + this.setValue(e.target.value) + }} + min={0} + /> + {unitLabel} +
    +
    this.setValue(addCurrencies(value, step, { toNumericBase: 'dec' }))} + > + +
    +
    this.setValue(subtractCurrencies(value, step, { toNumericBase: 'dec' }))} + > + +
    +
    +
    + ) } 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 97b16d08f..22419aabe 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 @@ -61,42 +61,46 @@ export default class LoadingNetworkScreen extends PureComponent { } renderLoadingScreenContent = () => { - return
    - - {this.renderMessage()} -
    + return ( +
    + + {this.renderMessage()} +
    + ) } renderErrorScreenContent = () => { const { showNetworkDropdown, setProviderArgs, setProviderType } = this.props - return
    - 😞 - { this.context.t('somethingWentWrong') } -
    - + return ( +
    + 😞 + { this.context.t('somethingWentWrong') } +
    + - + +
    -
    + ) } cancelCall = () => { diff --git a/ui/app/components/app/menu-droppo.js b/ui/app/components/app/menu-droppo.js index a88cad4b4..57a0df2fe 100644 --- a/ui/app/components/app/menu-droppo.js +++ b/ui/app/components/app/menu-droppo.js @@ -1,5 +1,4 @@ -const Component = require('react').Component -const h = require('react-hyperscript') +import React, { Component } from 'react' const inherits = require('util').inherits const findDOMNode = require('react-dom').findDOMNode const ReactCSSTransitionGroup = require('react-transition-group/CSSTransitionGroup') @@ -27,41 +26,44 @@ MenuDroppoComponent.prototype.render = function () { style.zIndex = zIndex return ( - h('div', { - style, - className: `.menu-droppo-container ${containerClassName}`, - }, [ - h('style', ` - .menu-droppo-enter { - transition: transform ${speed} ease-in-out; - transform: translateY(-200%); - } +
    + + { + useCssTransition + ? ( + + {this.renderPrimary()} + + ) + : this.renderPrimary() + } +
    ) } @@ -74,11 +76,9 @@ MenuDroppoComponent.prototype.renderPrimary = function () { const innerStyle = this.props.innerStyle || {} return ( - h('.menu-droppo', { - key: 'menu-droppo-drawer', - style: innerStyle, - }, - [ this.props.children ]) +
    + {this.props.children} +
    ) } @@ -98,7 +98,7 @@ MenuDroppoComponent.prototype.componentDidMount = function () { this.globalClickHandler = this.globalClickOccurred.bind(this) document.body.addEventListener('click', this.globalClickHandler) // eslint-disable-next-line react/no-find-dom-node - var container = findDOMNode(this) + const container = findDOMNode(this) this.container = container } } @@ -122,7 +122,7 @@ MenuDroppoComponent.prototype.globalClickOccurred = function (event) { } function isDescendant (parent, child) { - var node = child.parentNode + let node = child.parentNode while (node !== null) { if (node === parent) { return true diff --git a/ui/app/components/app/modal/modal.component.js b/ui/app/components/app/modal/modal.component.js index f0fdd3bd5..6c45160fd 100644 --- a/ui/app/components/app/modal/modal.component.js +++ b/ui/app/components/app/modal/modal.component.js @@ -16,6 +16,7 @@ export default class Modal extends PureComponent { submitType: PropTypes.string, submitText: PropTypes.string, submitDisabled: PropTypes.bool, + hideFooter: PropTypes.bool, // Cancel button (left button) onCancel: PropTypes.func, cancelType: PropTypes.string, @@ -41,6 +42,7 @@ export default class Modal extends PureComponent { cancelText, contentClass, containerClass, + hideFooter, } = this.props return ( @@ -61,27 +63,32 @@ export default class Modal extends PureComponent {
    { children }
    -
    - { - onCancel && ( + { !hideFooter + ? ( +
    + { + onCancel && ( + + ) + } - ) - } - -
    +
    + ) + : null + }
    ) } diff --git a/ui/app/components/app/modals/account-details-modal/account-details-modal.component.js b/ui/app/components/app/modals/account-details-modal/account-details-modal.component.js index 1b9a6a718..b2ed1a8cc 100644 --- a/ui/app/components/app/modals/account-details-modal/account-details-modal.component.js +++ b/ui/app/components/app/modals/account-details-modal/account-details-modal.component.js @@ -56,7 +56,7 @@ export default class AccountDetailsModal extends Component { }} /> -
    +
    + ? ( + + ) : null } diff --git a/ui/app/components/app/modals/account-modal-container.js b/ui/app/components/app/modals/account-modal-container.js deleted file mode 100644 index b7ae0b5b8..000000000 --- a/ui/app/components/app/modals/account-modal-container.js +++ /dev/null @@ -1,80 +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') -const { getSelectedIdentity } = require('../../../selectors/selectors') -import Identicon from '../../ui/identicon' - -function mapStateToProps (state, ownProps) { - return { - selectedIdentity: ownProps.selectedIdentity || getSelectedIdentity(state), - } -} - -function mapDispatchToProps (dispatch) { - return { - hideModal: () => { - dispatch(actions.hideModal()) - }, - } -} - -inherits(AccountModalContainer, Component) -function AccountModalContainer () { - Component.call(this) -} - -AccountModalContainer.contextTypes = { - t: PropTypes.func, -} - -module.exports = connect(mapStateToProps, mapDispatchToProps)(AccountModalContainer) - - -AccountModalContainer.prototype.render = function () { - const { - selectedIdentity, - showBackButton = false, - backButtonAction, - } = this.props - let { children } = this.props - - if (children.constructor !== Array) { - children = [children] - } - - return h('div', { style: { borderRadius: '4px' }}, [ - h('div.account-modal-container', [ - - h('div', [ - - // Needs a border; requires changes to svg - h(Identicon, { - address: selectedIdentity.address, - diameter: 64, - style: {}, - }), - - ]), - - showBackButton && h('div.account-modal-back', { - onClick: backButtonAction, - }, [ - - h('i.fa.fa-angle-left.fa-lg'), - - h('span.account-modal-back__text', ' ' + this.context.t('back')), - - ]), - - h('div.account-modal-close', { - onClick: this.props.hideModal, - }), - - ...children, - - ]), - ]) -} diff --git a/ui/app/components/app/modals/account-modal-container/account-modal-container.component.js b/ui/app/components/app/modals/account-modal-container/account-modal-container.component.js new file mode 100644 index 000000000..ed28f5fd1 --- /dev/null +++ b/ui/app/components/app/modals/account-modal-container/account-modal-container.component.js @@ -0,0 +1,52 @@ +import PropTypes from 'prop-types' +import React from 'react' +import Identicon from '../../../ui/identicon' + +export default function AccountModalContainer (props, context) { + const { + selectedIdentity, + showBackButton, + backButtonAction, + hideModal, + children, + } = props + + return ( +
    +
    +
    + +
    + {showBackButton && ( +
    + + {' ' + context.t('back')} +
    + )} +
    + {children} +
    +
    + ) +} + +AccountModalContainer.contextTypes = { + t: PropTypes.func, +} + +AccountModalContainer.defaultProps = { + showBackButton: false, + children: null, + backButtonAction: undefined, +} + +AccountModalContainer.propTypes = { + selectedIdentity: PropTypes.object.isRequired, + showBackButton: PropTypes.bool, + backButtonAction: PropTypes.func, + hideModal: PropTypes.func.isRequired, + children: PropTypes.node, +} diff --git a/ui/app/components/app/modals/account-modal-container/account-modal-container.container.js b/ui/app/components/app/modals/account-modal-container/account-modal-container.container.js new file mode 100644 index 000000000..ad118f92d --- /dev/null +++ b/ui/app/components/app/modals/account-modal-container/account-modal-container.container.js @@ -0,0 +1,20 @@ +import { connect } from 'react-redux' +import { hideModal } from '../../../../store/actions' +import { getSelectedIdentity } from '../../../../selectors/selectors' +import AccountModalContainer from './account-modal-container.component' + +function mapStateToProps (state, ownProps) { + return { + selectedIdentity: ownProps.selectedIdentity || getSelectedIdentity(state), + } +} + +function mapDispatchToProps (dispatch) { + return { + hideModal: () => { + dispatch(hideModal()) + }, + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(AccountModalContainer) diff --git a/ui/app/components/app/modals/account-modal-container/index.js b/ui/app/components/app/modals/account-modal-container/index.js new file mode 100644 index 000000000..e37684b96 --- /dev/null +++ b/ui/app/components/app/modals/account-modal-container/index.js @@ -0,0 +1 @@ +export { default } from './account-modal-container.container' diff --git a/ui/app/components/app/modals/clear-approved-origins/clear-approved-origins.component.js b/ui/app/components/app/modals/clear-approved-origins/clear-approved-origins.component.js deleted file mode 100644 index ceaa20a95..000000000 --- a/ui/app/components/app/modals/clear-approved-origins/clear-approved-origins.component.js +++ /dev/null @@ -1,39 +0,0 @@ -import React, { PureComponent } from 'react' -import PropTypes from 'prop-types' -import Modal, { ModalContent } from '../../modal' - -export default class ClearApprovedOrigins extends PureComponent { - static propTypes = { - hideModal: PropTypes.func.isRequired, - clearApprovedOrigins: PropTypes.func.isRequired, - } - - static contextTypes = { - t: PropTypes.func, - } - - handleClear = () => { - const { clearApprovedOrigins, hideModal } = this.props - clearApprovedOrigins() - hideModal() - } - - render () { - const { t } = this.context - - return ( - this.props.hideModal()} - submitText={t('ok')} - cancelText={t('nevermind')} - submitType="secondary" - > - - - ) - } -} diff --git a/ui/app/components/app/modals/clear-approved-origins/index.js b/ui/app/components/app/modals/clear-approved-origins/index.js deleted file mode 100644 index b3e321995..000000000 --- a/ui/app/components/app/modals/clear-approved-origins/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './clear-approved-origins.container' diff --git a/ui/app/components/app/modals/confirm-remove-account/confirm-remove-account.component.js b/ui/app/components/app/modals/confirm-remove-account/confirm-remove-account.component.js index 7fe79be5b..7bea983b4 100644 --- a/ui/app/components/app/modals/confirm-remove-account/confirm-remove-account.component.js +++ b/ui/app/components/app/modals/confirm-remove-account/confirm-remove-account.component.js @@ -78,7 +78,9 @@ export default class ConfirmRemoveAccount extends Component { + target="_blank" + href="https://metamask.zendesk.com/hc/en-us/articles/360015289932" + > { t('learnMore') }
    diff --git a/ui/app/components/app/modals/deposit-ether-modal.js b/ui/app/components/app/modals/deposit-ether-modal.js deleted file mode 100644 index f71e0619e..000000000 --- a/ui/app/components/app/modals/deposit-ether-modal.js +++ /dev/null @@ -1,213 +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') -const { getNetworkDisplayName } = require('../../../../../app/scripts/controllers/network/util') - -import Button from '../../ui/button' - -let DIRECT_DEPOSIT_ROW_TITLE -let DIRECT_DEPOSIT_ROW_TEXT -let WYRE_ROW_TITLE -let WYRE_ROW_TEXT -let FAUCET_ROW_TITLE -let COINSWITCH_ROW_TITLE -let COINSWITCH_ROW_TEXT - -function mapStateToProps (state) { - return { - network: state.metamask.network, - address: state.metamask.selectedAddress, - } -} - -function mapDispatchToProps (dispatch) { - return { - toWyre: (address) => { - dispatch(actions.buyEth({ service: 'wyre', address, amount: 0 })) - }, - toCoinSwitch: (address) => { - dispatch(actions.buyEth({ service: 'coinswitch', address })) - }, - hideModal: () => { - dispatch(actions.hideModal()) - }, - hideWarning: () => { - dispatch(actions.hideWarning()) - }, - showAccountDetailModal: () => { - dispatch(actions.showModal({ name: 'ACCOUNT_DETAILS' })) - }, - toFaucet: network => dispatch(actions.buyEth({ network })), - } -} - -inherits(DepositEtherModal, Component) -function DepositEtherModal (_, context) { - Component.call(this) - - // need to set after i18n locale has loaded - DIRECT_DEPOSIT_ROW_TITLE = context.t('directDepositEther') - DIRECT_DEPOSIT_ROW_TEXT = context.t('directDepositEtherExplainer') - WYRE_ROW_TITLE = context.t('buyWithWyre') - WYRE_ROW_TEXT = context.t('buyWithWyreDescription') - FAUCET_ROW_TITLE = context.t('testFaucet') - COINSWITCH_ROW_TITLE = context.t('buyCoinSwitch') - COINSWITCH_ROW_TEXT = context.t('buyCoinSwitchExplainer') -} - -DepositEtherModal.contextTypes = { - t: PropTypes.func, -} - -module.exports = connect(mapStateToProps, mapDispatchToProps)(DepositEtherModal) - - -DepositEtherModal.prototype.facuetRowText = function (networkName) { - return this.context.t('getEtherFromFaucet', [networkName]) -} - -DepositEtherModal.prototype.renderRow = function ({ - logo, - title, - text, - buttonLabel, - onButtonClick, - hide, - className, - hideButton, - hideTitle, - onBackClick, - showBackButton, -}) { - if (hide) { - return null - } - - return h('div', { - className: className || 'deposit-ether-modal__buy-row', - }, [ - - onBackClick && showBackButton && h('div.deposit-ether-modal__buy-row__back', { - onClick: onBackClick, - }, [ - - h('i.fa.fa-arrow-left.cursor-pointer'), - - ]), - - h('div.deposit-ether-modal__buy-row__logo-container', [logo]), - - h('div.deposit-ether-modal__buy-row__description', [ - - !hideTitle && h('div.deposit-ether-modal__buy-row__description__title', [title]), - - h('div.deposit-ether-modal__buy-row__description__text', [text]), - - ]), - - !hideButton && h('div.deposit-ether-modal__buy-row__button', [ - h(Button, { - type: 'secondary', - className: 'deposit-ether-modal__deposit-button', - large: true, - onClick: onButtonClick, - }, [buttonLabel]), - ]), - - ]) -} - -DepositEtherModal.prototype.render = function () { - const { network, toWyre, toCoinSwitch, address, toFaucet } = this.props - - 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', {}, [ - - h('div.page-container__header', [ - - h('div.page-container__title', [this.context.t('depositEther')]), - - h('div.page-container__subtitle', [ - this.context.t('needEtherInWallet'), - ]), - - h('div.page-container__header-close', { - onClick: () => { - this.props.hideWarning() - this.props.hideModal() - }, - }), - - ]), - - h('.page-container__content', {}, [ - - h('div.deposit-ether-modal__buy-rows', [ - - this.renderRow({ - logo: h('img.deposit-ether-modal__logo', { - src: './images/deposit-eth.svg', - style: { - height: '75px', - width: '75px', - }, - }), - title: DIRECT_DEPOSIT_ROW_TITLE, - text: DIRECT_DEPOSIT_ROW_TEXT, - buttonLabel: this.context.t('viewAccount'), - onButtonClick: () => this.goToAccountDetailsModal(), - }), - - this.renderRow({ - logo: h('i.fa.fa-tint.fa-2x'), - title: FAUCET_ROW_TITLE, - text: this.facuetRowText(networkName), - buttonLabel: this.context.t('getEther'), - onButtonClick: () => toFaucet(network), - hide: !isTestNetwork, - }), - - this.renderRow({ - logo: h('div.deposit-ether-modal__logo', { - style: { - backgroundImage: 'url(\'./images/wyre.svg\')', - height: '40px', - }, - }), - title: WYRE_ROW_TITLE, - text: WYRE_ROW_TEXT, - buttonLabel: this.context.t('continueToWyre'), - onButtonClick: () => toWyre(address), - hide: isTestNetwork, - }), - - this.renderRow({ - logo: h('div.deposit-ether-modal__logo', { - style: { - backgroundImage: 'url(\'./images/coinswitch_logo.png\')', - height: '40px', - }, - }), - title: COINSWITCH_ROW_TITLE, - text: COINSWITCH_ROW_TEXT, - buttonLabel: this.context.t('continueToCoinSwitch'), - onButtonClick: () => toCoinSwitch(address), - hide: isTestNetwork, - }), - - ]), - - ]), - ]) -} - -DepositEtherModal.prototype.goToAccountDetailsModal = function () { - this.props.hideWarning() - this.props.hideModal() - this.props.showAccountDetailModal() -} diff --git a/ui/app/components/app/modals/deposit-ether-modal/deposit-ether-modal.component.js b/ui/app/components/app/modals/deposit-ether-modal/deposit-ether-modal.component.js new file mode 100644 index 000000000..d503d950e --- /dev/null +++ b/ui/app/components/app/modals/deposit-ether-modal/deposit-ether-modal.component.js @@ -0,0 +1,168 @@ +import PropTypes from 'prop-types' +import React, {Component} from 'react' +import {getNetworkDisplayName} from '../../../../../../app/scripts/controllers/network/util' +import Button from '../../../ui/button' + +export default class DepositEtherModal extends Component { + static contextTypes = { + t: PropTypes.func, + } + + static propTypes = { + network: PropTypes.string.isRequired, + toWyre: PropTypes.func.isRequired, + toCoinSwitch: PropTypes.func.isRequired, + address: PropTypes.string.isRequired, + toFaucet: PropTypes.func.isRequired, + hideWarning: PropTypes.func.isRequired, + hideModal: PropTypes.func.isRequired, + showAccountDetailModal: PropTypes.func.isRequired, + } + + faucetRowText = (networkName) => { + return this.context.t('getEtherFromFaucet', [networkName]) + } + + goToAccountDetailsModal = () => { + this.props.hideWarning() + this.props.hideModal() + this.props.showAccountDetailModal() + } + + renderRow ({ + logo, + title, + text, + buttonLabel, + onButtonClick, + hide, + className, + hideButton, + hideTitle, + onBackClick, + showBackButton, + }) { + if (hide) { + return null + } + + return ( +
    + {onBackClick && showBackButton && ( +
    + +
    + )} +
    {logo}
    +
    + {!hideTitle && ( +
    {title}
    + )} +
    {text}
    +
    + {!hideButton && ( +
    + +
    + )} +
    + ) + } + + render () { + const { network, toWyre, toCoinSwitch, address, toFaucet } = this.props + + const isTestNetwork = ['3', '4', '5', '42'].find(n => n === network) + const networkName = getNetworkDisplayName(network) + + return ( +
    +
    +
    + {this.context.t('depositEther')} +
    +
    + {this.context.t('needEtherInWallet')} +
    +
    { + this.props.hideWarning() + this.props.hideModal() + }} + /> +
    +
    +
    + {this.renderRow({ + logo: ( + + ), + title: this.context.t('directDepositEther'), + text: this.context.t('directDepositEtherExplainer'), + buttonLabel: this.context.t('viewAccount'), + onButtonClick: () => this.goToAccountDetailsModal(), + })} + {this.renderRow({ + logo: , + title: this.context.t('testFaucet'), + text: this.faucetRowText(networkName), + buttonLabel: this.context.t('getEther'), + onButtonClick: () => toFaucet(network), + hide: !isTestNetwork, + })} + {this.renderRow({ + logo: ( +
    + ), + title: this.context.t('buyWithWyre'), + text: this.context.t('buyWithWyreDescription'), + buttonLabel: this.context.t('continueToWyre'), + onButtonClick: () => toWyre(address), + hide: isTestNetwork, + })} + {this.renderRow({ + logo: ( +
    + ), + title: this.context.t('buyCoinSwitch'), + text: this.context.t('buyCoinSwitchExplainer'), + buttonLabel: this.context.t('continueToCoinSwitch'), + onButtonClick: () => toCoinSwitch(address), + hide: isTestNetwork, + })} +
    +
    +
    + ) + } +} diff --git a/ui/app/components/app/modals/deposit-ether-modal/deposit-ether-modal.container.js b/ui/app/components/app/modals/deposit-ether-modal/deposit-ether-modal.container.js new file mode 100644 index 000000000..cccbe7667 --- /dev/null +++ b/ui/app/components/app/modals/deposit-ether-modal/deposit-ether-modal.container.js @@ -0,0 +1,34 @@ +import { connect } from 'react-redux' +import { buyEth, hideModal, showModal, hideWarning } from '../../../../store/actions' +import DepositEtherModal from './deposit-ether-modal.component' + +function mapStateToProps (state) { + return { + network: state.metamask.network, + address: state.metamask.selectedAddress, + } +} + +function mapDispatchToProps (dispatch) { + return { + toWyre: (address) => { + dispatch(buyEth({ service: 'wyre', address, amount: 0 })) + }, + toCoinSwitch: (address) => { + dispatch(buyEth({ service: 'coinswitch', address })) + }, + hideModal: () => { + dispatch(hideModal()) + }, + hideWarning: () => { + dispatch(hideWarning()) + }, + showAccountDetailModal: () => { + dispatch(showModal({ name: 'ACCOUNT_DETAILS' })) + }, + toFaucet: network => dispatch(buyEth({ network })), + } +} + + +export default connect(mapStateToProps, mapDispatchToProps)(DepositEtherModal) diff --git a/ui/app/components/app/modals/deposit-ether-modal/index.js b/ui/app/components/app/modals/deposit-ether-modal/index.js new file mode 100644 index 000000000..01a262a73 --- /dev/null +++ b/ui/app/components/app/modals/deposit-ether-modal/index.js @@ -0,0 +1 @@ +export { default } from './deposit-ether-modal.container' diff --git a/ui/app/components/app/modals/disconnect-account/disconnect-account.component.js b/ui/app/components/app/modals/disconnect-account/disconnect-account.component.js new file mode 100644 index 000000000..4fe5c7227 --- /dev/null +++ b/ui/app/components/app/modals/disconnect-account/disconnect-account.component.js @@ -0,0 +1,52 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import Modal from '../../modal' +import Button from '../../../ui/button' + + +export default class DisconnectAccount extends PureComponent { + static propTypes = { + hideModal: PropTypes.func.isRequired, + disconnectAccount: PropTypes.func.isRequired, + accountLabel: PropTypes.string.isRequired, + } + + static contextTypes = { + t: PropTypes.func, + } + + render () { + const { t } = this.context + const { hideModal, disconnectAccount, accountLabel } = this.props + + return ( + hideModal()} + hideFooter + > +
    +
    + { t('disconnectAccountModalDescription', [ accountLabel ]) } +
    + + +
    +
    + ) + } +} diff --git a/ui/app/components/app/modals/disconnect-account/disconnect-account.container.js b/ui/app/components/app/modals/disconnect-account/disconnect-account.container.js new file mode 100644 index 000000000..b0511bb47 --- /dev/null +++ b/ui/app/components/app/modals/disconnect-account/disconnect-account.container.js @@ -0,0 +1,44 @@ +import { connect } from 'react-redux' +import { compose } from 'recompose' +import withModalProps from '../../../../helpers/higher-order-components/with-modal-props' +import DisconnectAccount from './disconnect-account.component' +import { getCurrentAccountWithSendEtherInfo } from '../../../../selectors/selectors' +import { removePermissionsFor } from '../../../../store/actions' + +const mapStateToProps = state => { + return { + ...state.appState.modal.modalState.props || {}, + accountLabel: getCurrentAccountWithSendEtherInfo(state).name, + } +} + +const mapDispatchToProps = dispatch => { + return { + disconnectAccount: (domainKey, domain) => { + const permissionMethodNames = domain.permissions.map(perm => perm.parentCapability) + dispatch(removePermissionsFor({ [domainKey]: permissionMethodNames })) + }, + } +} + +const mergeProps = (stateProps, dispatchProps, ownProps) => { + const { + domainKey, + domain, + } = stateProps + const { + disconnectAccount: dispatchDisconnectAccount, + } = dispatchProps + + return { + ...ownProps, + ...stateProps, + ...dispatchProps, + disconnectAccount: () => dispatchDisconnectAccount(domainKey, domain), + } +} + +export default compose( + withModalProps, + connect(mapStateToProps, mapDispatchToProps, mergeProps) +)(DisconnectAccount) diff --git a/ui/app/components/app/modals/disconnect-account/index.js b/ui/app/components/app/modals/disconnect-account/index.js new file mode 100644 index 000000000..43bfac9fd --- /dev/null +++ b/ui/app/components/app/modals/disconnect-account/index.js @@ -0,0 +1 @@ +export { default } from './disconnect-account.container' diff --git a/ui/app/components/app/modals/disconnect-account/index.scss b/ui/app/components/app/modals/disconnect-account/index.scss new file mode 100644 index 000000000..861b7cec2 --- /dev/null +++ b/ui/app/components/app/modals/disconnect-account/index.scss @@ -0,0 +1,25 @@ +.disconnect-account-modal { + &__description { + color: #24292E; + margin-bottom: 24px; + } + + &__cancel-button { + border: none; + margin-top: 12px; + } +} + +.disconnect-account-modal-container { + .modal-container__header-text { + @extend %header--18; + } + + .modal-container__content { + padding-bottom: 18px; + + @media screen and (max-width: 575px) { + padding-bottom: 18px; + } + } +} \ No newline at end of file diff --git a/ui/app/components/app/modals/disconnect-all/disconnect-all.component.js b/ui/app/components/app/modals/disconnect-all/disconnect-all.component.js new file mode 100644 index 000000000..2d29fd9ea --- /dev/null +++ b/ui/app/components/app/modals/disconnect-all/disconnect-all.component.js @@ -0,0 +1,54 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' +import Modal from '../../modal' +import Button from '../../../ui/button' +import { DEFAULT_ROUTE } from '../../../../helpers/constants/routes' + +export default class DisconnectAll extends PureComponent { + static propTypes = { + hideModal: PropTypes.func.isRequired, + disconnectAll: PropTypes.func.isRequired, + history: PropTypes.object.isRequired, + } + + static contextTypes = { + t: PropTypes.func, + } + + render () { + const { t } = this.context + const { hideModal, disconnectAll, history } = this.props + + return ( + hideModal()} + hideFooter + > +
    +
    + { t('disconnectAllModalDescription') } +
    + + +
    +
    + ) + } +} diff --git a/ui/app/components/app/modals/clear-approved-origins/clear-approved-origins.container.js b/ui/app/components/app/modals/disconnect-all/disconnect-all.container.js similarity index 53% rename from ui/app/components/app/modals/clear-approved-origins/clear-approved-origins.container.js rename to ui/app/components/app/modals/disconnect-all/disconnect-all.container.js index 2276bc7e7..2415c3fa9 100644 --- a/ui/app/components/app/modals/clear-approved-origins/clear-approved-origins.container.js +++ b/ui/app/components/app/modals/disconnect-all/disconnect-all.container.js @@ -1,16 +1,20 @@ import { connect } from 'react-redux' import { compose } from 'recompose' +import { withRouter } from 'react-router-dom' import withModalProps from '../../../../helpers/higher-order-components/with-modal-props' -import ClearApprovedOriginsComponent from './clear-approved-origins.component' -import { clearApprovedOrigins } from '../../../../store/actions' +import DisconnectAll from './disconnect-all.component' +import { clearPermissions } from '../../../../store/actions' const mapDispatchToProps = dispatch => { return { - clearApprovedOrigins: () => dispatch(clearApprovedOrigins()), + disconnectAll: () => { + dispatch(clearPermissions()) + }, } } export default compose( withModalProps, + withRouter, connect(null, mapDispatchToProps) -)(ClearApprovedOriginsComponent) +)(DisconnectAll) diff --git a/ui/app/components/app/modals/disconnect-all/index.js b/ui/app/components/app/modals/disconnect-all/index.js new file mode 100644 index 000000000..7fdfac530 --- /dev/null +++ b/ui/app/components/app/modals/disconnect-all/index.js @@ -0,0 +1 @@ +export { default } from './disconnect-all.container' diff --git a/ui/app/components/app/modals/disconnect-all/index.scss b/ui/app/components/app/modals/disconnect-all/index.scss new file mode 100644 index 000000000..8f69baade --- /dev/null +++ b/ui/app/components/app/modals/disconnect-all/index.scss @@ -0,0 +1,38 @@ +.disconnect-all-modal { + height: 160px; + display: flex; + flex-direction: column; + justify-content: space-between; + + &__description { + color: #24292E; + margin-bottom: 24px; + } + + &__cancel-button { + border: none; + margin-top: 12px; + } + + .btn-secondary { + border: none; + } +} + +.disconnect-all-modal-container { + .modal-container__header-text { + font-family: Roboto; + font-style: normal; + font-weight: bold; + font-size: 18px; + color: #24292E; + } + + .modal-container__content { + padding-bottom: 18px; + + @media screen and (max-width: 575px) { + padding-bottom: 18px; + } + } +} diff --git a/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.component.js b/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.component.js index f627ddaef..7c850279b 100644 --- a/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.component.js +++ b/ui/app/components/app/modals/edit-approval-permission/edit-approval-permission.component.js @@ -82,18 +82,22 @@ export default class EditApprovalPermission extends PureComponent { className="edit-approval-permission__edit-section__radio-button" onClick={() => this.setState({ selectedOptionIsUnlimited: true })} > -
    +
    { selectedOptionIsUnlimited &&
    }
    -
    +
    { (new BigNumber(tokenAmount)).lessThan(new BigNumber(tokenBalance)) ? t('proposedApprovalLimit') @@ -113,18 +117,22 @@ export default class EditApprovalPermission extends PureComponent { className="edit-approval-permission__edit-section__radio-button" onClick={() => this.setState({ selectedOptionIsUnlimited: false })} > -
    +
    { !selectedOptionIsUnlimited &&
    }
    -
    +
    { t('customSpendLimit') }
    diff --git a/ui/app/components/app/modals/export-private-key-modal.js b/ui/app/components/app/modals/export-private-key-modal.js deleted file mode 100644 index 1e1aaeb74..000000000 --- a/ui/app/components/app/modals/export-private-key-modal.js +++ /dev/null @@ -1,178 +0,0 @@ -const log = require('loglevel') -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 { stripHexPrefix } = require('ethereumjs-util') -const actions = require('../../../store/actions') -const AccountModalContainer = require('./account-modal-container') -const { getSelectedIdentity } = require('../../../selectors/selectors') -const ReadOnlyInput = require('../../ui/readonly-input') -const copyToClipboard = require('copy-to-clipboard') -const { checksumAddress } = require('../../../helpers/utils/util') -import Button from '../../ui/button' - -function mapStateToPropsFactory () { - let selectedIdentity = null - return function mapStateToProps (state) { - // We should **not** change the identity displayed here even if it changes from underneath us. - // If we do, we will be showing the user one private key and a **different** address and name. - // Note that the selected identity **will** change from underneath us when we unlock the keyring - // which is the expected behavior that we are side-stepping. - selectedIdentity = selectedIdentity || getSelectedIdentity(state) - return { - warning: state.appState.warning, - privateKey: state.appState.accountDetail.privateKey, - network: state.metamask.network, - selectedIdentity, - previousModalState: state.appState.modal.previousModalState.name, - } - } -} - -function mapDispatchToProps (dispatch) { - return { - exportAccount: (password, address) => { - return dispatch(actions.exportAccount(password, address)) - .then((res) => { - dispatch(actions.hideWarning()) - return res - }) - }, - showAccountDetailModal: () => dispatch(actions.showModal({ name: 'ACCOUNT_DETAILS' })), - hideModal: () => dispatch(actions.hideModal()), - } -} - -inherits(ExportPrivateKeyModal, Component) -function ExportPrivateKeyModal () { - Component.call(this) - - this.state = { - password: '', - privateKey: null, - showWarning: true, - } -} - -ExportPrivateKeyModal.contextTypes = { - t: PropTypes.func, -} - -module.exports = connect(mapStateToPropsFactory, mapDispatchToProps)(ExportPrivateKeyModal) - - -ExportPrivateKeyModal.prototype.exportAccountAndGetPrivateKey = function (password, address) { - const { exportAccount } = this.props - - exportAccount(password, address) - .then(privateKey => this.setState({ - privateKey, - showWarning: false, - })) - .catch((e) => log.error(e)) -} - -ExportPrivateKeyModal.prototype.renderPasswordLabel = function (privateKey) { - return h('span.private-key-password-label', privateKey - ? this.context.t('copyPrivateKey') - : this.context.t('typePassword') - ) -} - -ExportPrivateKeyModal.prototype.renderPasswordInput = function (privateKey) { - const plainKey = privateKey && stripHexPrefix(privateKey) - - return privateKey - ? h(ReadOnlyInput, { - wrapperClass: 'private-key-password-display-wrapper', - inputClass: 'private-key-password-display-textarea', - textarea: true, - value: plainKey, - onClick: () => copyToClipboard(plainKey), - }) - : h('input.private-key-password-input', { - type: 'password', - onChange: event => this.setState({ password: event.target.value }), - }) -} - -ExportPrivateKeyModal.prototype.renderButtons = function (privateKey, address, hideModal) { - return h('div.export-private-key-buttons', {}, [ - !privateKey && h(Button, { - type: 'default', - large: true, - className: 'export-private-key__button export-private-key__button--cancel', - onClick: () => hideModal(), - }, this.context.t('cancel')), - - (privateKey - ? ( - h(Button, { - type: 'secondary', - large: true, - className: 'export-private-key__button', - onClick: () => hideModal(), - }, this.context.t('done')) - ) : ( - h(Button, { - type: 'secondary', - large: true, - className: 'export-private-key__button', - disabled: !this.state.password, - onClick: () => this.exportAccountAndGetPrivateKey(this.state.password, address), - }, this.context.t('confirm')) - ) - ), - - ]) -} - -ExportPrivateKeyModal.prototype.render = function () { - const { - selectedIdentity, - warning, - showAccountDetailModal, - hideModal, - previousModalState, - } = this.props - const { name, address } = selectedIdentity - - const { - privateKey, - showWarning, - } = this.state - - return h(AccountModalContainer, { - selectedIdentity, - showBackButton: previousModalState === 'ACCOUNT_DETAILS', - backButtonAction: () => showAccountDetailModal(), - }, [ - - h('span.account-name', name), - - h(ReadOnlyInput, { - wrapperClass: 'ellip-address-wrapper', - inputClass: 'qr-ellip-address ellip-address', - value: checksumAddress(address), - }), - - h('div.account-modal-divider'), - - h('span.modal-body-title', this.context.t('showPrivateKeys')), - - h('div.private-key-password', {}, [ - this.renderPasswordLabel(privateKey), - - this.renderPasswordInput(privateKey), - - showWarning && warning ? h('span.private-key-password-error', warning) : null, - ]), - - h('div.private-key-password-warning', this.context.t('privateKeyWarning')), - - this.renderButtons(privateKey, address, hideModal), - - ]) -} diff --git a/ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.component.js b/ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.component.js new file mode 100644 index 000000000..fd70cbc5b --- /dev/null +++ b/ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.component.js @@ -0,0 +1,168 @@ +import log from 'loglevel' +import PropTypes from 'prop-types' +import React, { Component } from 'react' + +const { stripHexPrefix } = require('ethereumjs-util') +const copyToClipboard = require('copy-to-clipboard') +const { checksumAddress } = require('../../../../helpers/utils/util') +const ReadOnlyInput = require('../../../ui/readonly-input') +import Button from '../../../ui/button' +import AccountModalContainer from '../account-modal-container' + +export default class ExportPrivateKeyModal extends Component { + static contextTypes = { + t: PropTypes.func, + } + + static defaultProps = { + warning: null, + previousModalState: null, + } + + static propTypes = { + exportAccount: PropTypes.func.isRequired, + selectedIdentity: PropTypes.object.isRequired, + warning: PropTypes.node, + showAccountDetailModal: PropTypes.func.isRequired, + hideModal: PropTypes.func.isRequired, + previousModalState: PropTypes.string, + } + + state = { + password: '', + privateKey: null, + showWarning: true, + } + + exportAccountAndGetPrivateKey = (password, address) => { + const { exportAccount } = this.props + + exportAccount(password, address) + .then(privateKey => this.setState({ + privateKey, + showWarning: false, + })) + .catch((e) => log.error(e)) + } + + renderPasswordLabel (privateKey) { + return ( + + { + privateKey + ? this.context.t('copyPrivateKey') + : this.context.t('typePassword') + } + + ) + } + + renderPasswordInput (privateKey) { + const plainKey = privateKey && stripHexPrefix(privateKey) + + if (!privateKey) { + return ( + this.setState({ password: event.target.value })} + /> + ) + } + + return ( + copyToClipboard(plainKey)} + /> + ) + } + + renderButtons (privateKey, address, hideModal) { + return ( +
    + {!privateKey && ( + + )} + { + privateKey + ? ( + + ) + : ( + + ) + } +
    + ) + } + + render () { + const { + selectedIdentity, + warning, + showAccountDetailModal, + hideModal, + previousModalState, + } = this.props + const { name, address } = selectedIdentity + + const { + privateKey, + showWarning, + } = this.state + + return ( + showAccountDetailModal()} + > + {name} + +
    + {this.context.t('showPrivateKeys')} +
    + {this.renderPasswordLabel(privateKey)} + {this.renderPasswordInput(privateKey)} + { + (showWarning && warning) + ? {warning} + : null + } +
    +
    {this.context.t('privateKeyWarning')}
    + {this.renderButtons(privateKey, address, hideModal)} + + ) + } +} diff --git a/ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.container.js b/ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.container.js new file mode 100644 index 000000000..1b8f63c94 --- /dev/null +++ b/ui/app/components/app/modals/export-private-key-modal/export-private-key-modal.container.js @@ -0,0 +1,38 @@ +import { connect } from 'react-redux' +import { exportAccount, hideWarning, showModal, hideModal } from '../../../../store/actions' +import { getSelectedIdentity } from '../../../../selectors/selectors' +import ExportPrivateKeyModal from './export-private-key-modal.component' + +function mapStateToPropsFactory () { + let selectedIdentity = null + return function mapStateToProps (state) { + // We should **not** change the identity displayed here even if it changes from underneath us. + // If we do, we will be showing the user one private key and a **different** address and name. + // Note that the selected identity **will** change from underneath us when we unlock the keyring + // which is the expected behavior that we are side-stepping. + selectedIdentity = selectedIdentity || getSelectedIdentity(state) + return { + warning: state.appState.warning, + privateKey: state.appState.accountDetail.privateKey, + network: state.metamask.network, + selectedIdentity, + previousModalState: state.appState.modal.previousModalState.name, + } + } +} + +function mapDispatchToProps (dispatch) { + return { + exportAccount: (password, address) => { + return dispatch(exportAccount(password, address)) + .then((res) => { + dispatch(hideWarning()) + return res + }) + }, + showAccountDetailModal: () => dispatch(showModal({ name: 'ACCOUNT_DETAILS' })), + hideModal: () => dispatch(hideModal()), + } +} + +export default connect(mapStateToPropsFactory, mapDispatchToProps)(ExportPrivateKeyModal) diff --git a/ui/app/components/app/modals/export-private-key-modal/index.js b/ui/app/components/app/modals/export-private-key-modal/index.js new file mode 100644 index 000000000..996c995ca --- /dev/null +++ b/ui/app/components/app/modals/export-private-key-modal/index.js @@ -0,0 +1 @@ +export { default } from './export-private-key-modal.container' 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 e2b098923..c3709e923 100644 --- a/ui/app/components/app/modals/hide-token-confirmation-modal.js +++ b/ui/app/components/app/modals/hide-token-confirmation-modal.js @@ -1,6 +1,5 @@ -const Component = require('react').Component -const PropTypes = require('prop-types') -const h = require('react-hyperscript') +import PropTypes from 'prop-types' +import React, { Component } from 'react' const inherits = require('util').inherits const connect = require('react-redux').connect const actions = require('../../../store/actions') @@ -40,44 +39,43 @@ HideTokenConfirmationModal.contextTypes = { module.exports = connect(mapStateToProps, mapDispatchToProps)(HideTokenConfirmationModal) -HideTokenConfirmationModal.prototype.render = function () { +HideTokenConfirmationModal.prototype.render = function HideTokenConfirmationModal () { const { token, network, hideToken, hideModal, assetImages } = this.props const { symbol, address } = token const image = assetImages[address] - return h('div.hide-token-confirmation', {}, [ - h('div.hide-token-confirmation__container', { - }, [ - h('div.hide-token-confirmation__title', {}, [ - this.context.t('hideTokenPrompt'), - ]), - - h(Identicon, { - className: 'hide-token-confirmation__identicon', - diameter: 45, - address, - network, - image, - }), - - h('div.hide-token-confirmation__symbol', {}, symbol), - - h('div.hide-token-confirmation__copy', {}, [ - this.context.t('readdToken'), - ]), - - h('div.hide-token-confirmation__buttons', {}, [ - h('button.btn-default.hide-token-confirmation__button.btn--large', { - onClick: () => hideModal(), - }, [ - this.context.t('cancel'), - ]), - h('button.btn-secondary.hide-token-confirmation__button.btn--large', { - onClick: () => hideToken(address), - }, [ - this.context.t('hide'), - ]), - ]), - ]), - ]) + return ( +
    +
    +
    + {this.context.t('hideTokenPrompt')} +
    + +
    {symbol}
    +
    + {this.context.t('readdToken')} +
    +
    + + +
    +
    +
    + ) } diff --git a/ui/app/components/app/modals/index.scss b/ui/app/components/app/modals/index.scss index da7a27b84..dbf47265f 100644 --- a/ui/app/components/app/modals/index.scss +++ b/ui/app/components/app/modals/index.scss @@ -11,3 +11,9 @@ @import './add-to-addressbook-modal/index'; @import './edit-approval-permission/index'; + +@import './disconnect-account/index'; + +@import './disconnect-all/index'; + +@import './new-account-modal/index'; diff --git a/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js b/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js index 6f3225382..9dc953315 100644 --- a/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js +++ b/ui/app/components/app/modals/metametrics-opt-in-modal/metametrics-opt-in-modal.component.js @@ -75,7 +75,8 @@ export default class MetaMetricsOptInModal extends Component {
    - This data is aggregated and is therefore anonymous for the purposes of General Data Protection Regulation (EU) 2016/679. For more information in relation to our privacy practices, please see our , onHide: (props) => props.hideWarning(), mobileModalStyle: { width: '100%', @@ -115,9 +115,88 @@ const MODALS = { }, ADD_TO_ADDRESSBOOK: { - contents: [ - h(AddToAddressBookModal, {}, []), - ], + contents: , + mobileModalStyle: { + width: '95%', + top: '10%', + boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px', + transform: 'none', + left: '0', + right: '0', + margin: '0 auto', + borderRadius: '10px', + }, + laptopModalStyle: { + width: '375px', + top: '10%', + boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px', + transform: 'none', + left: '0', + right: '0', + margin: '0 auto', + borderRadius: '10px', + }, + contentStyle: { + borderRadius: '10px', + }, + }, + + NEW_ACCOUNT: { + contents: , + mobileModalStyle: { + width: '95%', + top: '10%', + boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px', + transform: 'none', + left: '0', + right: '0', + margin: '0 auto', + borderRadius: '10px', + }, + laptopModalStyle: { + width: '375px', + top: '10%', + boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px', + transform: 'none', + left: '0', + right: '0', + margin: '0 auto', + borderRadius: '10px', + }, + contentStyle: { + borderRadius: '10px', + }, + }, + + DISCONNECT_ACCOUNT: { + contents: , + mobileModalStyle: { + width: '95%', + top: '10%', + boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px', + transform: 'none', + left: '0', + right: '0', + margin: '0 auto', + borderRadius: '10px', + }, + laptopModalStyle: { + width: '375px', + top: '10%', + boxShadow: 'rgba(0, 0, 0, 0.15) 0px 2px 2px 2px', + transform: 'none', + left: '0', + right: '0', + margin: '0 auto', + borderRadius: '10px', + }, + contentStyle: { + borderRadius: '10px', + }, + }, + + DISCONNECT_ALL: { + contents: , mobileModalStyle: { width: '95%', top: '10%', @@ -144,23 +223,17 @@ const MODALS = { }, ACCOUNT_DETAILS: { - contents: [ - h(AccountDetailsModal, {}, []), - ], + contents: , ...accountModalStyle, }, EXPORT_PRIVATE_KEY: { - contents: [ - h(ExportPrivateKeyModal, {}, []), - ], + contents: , ...accountModalStyle, }, HIDE_TOKEN_CONFIRMATION: { - contents: [ - h(HideTokenConfirmationModal, {}, []), - ], + contents: , mobileModalStyle: { width: '95%', top: getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP ? '52vh' : '36.5vh', @@ -171,21 +244,8 @@ const MODALS = { }, }, - CLEAR_APPROVED_ORIGINS: { - contents: h(ClearApprovedOrigins), - mobileModalStyle: { - ...modalContainerMobileStyle, - }, - laptopModalStyle: { - ...modalContainerLaptopStyle, - }, - contentStyle: { - borderRadius: '8px', - }, - }, - METAMETRICS_OPT_IN_MODAL: { - contents: h(MetaMetricsOptInModal), + contents: , mobileModalStyle: { ...modalContainerMobileStyle, width: '100%', @@ -202,12 +262,7 @@ const MODALS = { }, GAS_PRICE_INFO_MODAL: { - contents: [ - h(NotifcationModal, { - header: 'gasPriceNoDenom', - message: 'gasPriceInfoModalContent', - }), - ], + contents: , mobileModalStyle: { width: '95%', top: getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP ? '52vh' : '36.5vh', @@ -219,12 +274,7 @@ const MODALS = { }, GAS_LIMIT_INFO_MODAL: { - contents: [ - h(NotifcationModal, { - header: 'gasLimit', - message: 'gasLimitInfoModalContent', - }), - ], + contents: , mobileModalStyle: { width: '95%', top: getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP ? '52vh' : '36.5vh', @@ -236,7 +286,7 @@ const MODALS = { }, CONFIRM_RESET_ACCOUNT: { - contents: h(ConfirmResetAccount), + contents: , mobileModalStyle: { ...modalContainerMobileStyle, }, @@ -249,7 +299,7 @@ const MODALS = { }, CONFIRM_REMOVE_ACCOUNT: { - contents: h(ConfirmRemoveAccount), + contents: , mobileModalStyle: { ...modalContainerMobileStyle, }, @@ -262,7 +312,7 @@ const MODALS = { }, CONFIRM_DELETE_NETWORK: { - contents: h(ConfirmDeleteNetwork), + contents: , mobileModalStyle: { ...modalContainerMobileStyle, }, @@ -275,9 +325,7 @@ const MODALS = { }, CUSTOMIZE_GAS: { - contents: [ - h(ConfirmCustomizeGasModal), - ], + contents: , mobileModalStyle: { width: '100vw', height: '100vh', @@ -306,7 +354,7 @@ const MODALS = { }, EDIT_APPROVAL_PERMISSION: { - contents: h(EditApprovalPermission), + contents: , mobileModalStyle: { width: '95vw', height: '100vh', @@ -332,7 +380,7 @@ const MODALS = { TRANSACTION_CONFIRMED: { disableBackdropClick: true, - contents: h(TransactionConfirmed), + contents: , mobileModalStyle: { ...modalContainerMobileStyle, }, @@ -345,7 +393,7 @@ const MODALS = { }, QR_SCANNER: { - contents: h(QRScanner), + contents: , mobileModalStyle: { ...modalContainerMobileStyle, }, @@ -358,7 +406,7 @@ const MODALS = { }, CANCEL_TRANSACTION: { - contents: h(CancelTransaction), + contents: , mobileModalStyle: { ...modalContainerMobileStyle, }, @@ -371,7 +419,7 @@ const MODALS = { }, REJECT_TRANSACTIONS: { - contents: h(RejectTransactions), + contents: , mobileModalStyle: { ...modalContainerMobileStyle, }, @@ -430,25 +478,26 @@ Modal.prototype.render = function () { const modalStyle = modal[isMobileView() ? 'mobileModalStyle' : 'laptopModalStyle'] const contentStyle = modal.contentStyle || {} - return h(FadeModal, - { - className: 'modal', - keyboard: false, - onHide: () => { + return ( + { if (modal.onHide) { modal.onHide(this.props) } this.onHide(modal.customOnHideOpts) - }, - ref: (ref) => { + }} + ref={(ref) => { this.modalRef = ref - }, - modalStyle, - contentStyle, - backdropStyle: BACKDROPSTYLE, - closeOnClick: !disableBackdropClick, - }, - children, + }} + modalStyle={modalStyle} + contentStyle={contentStyle} + backdropStyle={BACKDROPSTYLE} + closeOnClick={!disableBackdropClick} + > + {children} + ) } diff --git a/ui/app/components/app/modals/new-account-modal/index.js b/ui/app/components/app/modals/new-account-modal/index.js new file mode 100644 index 000000000..2c8b78890 --- /dev/null +++ b/ui/app/components/app/modals/new-account-modal/index.js @@ -0,0 +1 @@ +export { default } from './new-account-modal.container' diff --git a/ui/app/components/app/modals/new-account-modal/index.scss b/ui/app/components/app/modals/new-account-modal/index.scss new file mode 100644 index 000000000..d6c2d0ac1 --- /dev/null +++ b/ui/app/components/app/modals/new-account-modal/index.scss @@ -0,0 +1,37 @@ +.new-account-modal { + @extend %col-nowrap; + @extend %modal; + + &__content { + @extend %col-nowrap; + padding: 1.5rem; + border-bottom: 1px solid $Grey-100; + + &__header { + @extend %h3; + } + } + + &__input-label { + color: $Grey-600; + margin-top: 1.25rem; + } + + &__input { + @extend %input; + margin-top: 0.75rem; + + &::placeholder { + color: $Grey-300; + } + } + + &__footer { + @extend %row-nowrap; + padding: 1rem; + + button + button { + margin-left: 1rem; + } + } +} diff --git a/ui/app/components/app/modals/new-account-modal/new-account-modal.component.js b/ui/app/components/app/modals/new-account-modal/new-account-modal.component.js new file mode 100644 index 000000000..26224ae63 --- /dev/null +++ b/ui/app/components/app/modals/new-account-modal/new-account-modal.component.js @@ -0,0 +1,78 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import Button from '../../../ui/button/button.component' + +export default class NewAccountModal extends Component { + + static contextTypes = { + t: PropTypes.func, + } + + static propTypes = { + hideModal: PropTypes.func.isRequired, + newAccountNumber: PropTypes.number.isRequired, + onSave: PropTypes.func.isRequired, + } + + state = { + alias: '', + } + + onChange = e => { + this.setState({ + alias: e.target.value, + }) + } + + onSubmit = () => { + this.props.onSave(this.state.alias) + .then(this.props.hideModal) + } + + onKeyPress = e => { + if (e.key === 'Enter' && this.state.alias) { + this.onSubmit() + } + } + + render () { + const { t } = this.context + + return ( +
    +
    +
    + {t('newAccount')} +
    +
    + {t('accountName')} +
    + +
    +
    + + +
    +
    + ) + } +} diff --git a/ui/app/components/app/modals/new-account-modal/new-account-modal.container.js b/ui/app/components/app/modals/new-account-modal/new-account-modal.container.js new file mode 100644 index 000000000..812e98dbd --- /dev/null +++ b/ui/app/components/app/modals/new-account-modal/new-account-modal.container.js @@ -0,0 +1,44 @@ +import { connect } from 'react-redux' +import NewAccountModal from './new-account-modal.component' +import actions from '../../../../store/actions' + +function mapStateToProps (state) { + return { + ...state.appState.modal.modalState.props || {}, + } +} + +function mapDispatchToProps (dispatch) { + return { + hideModal: () => dispatch(actions.hideModal()), + createAccount: newAccountName => { + return dispatch(actions.addNewAccount()) + .then(newAccountAddress => { + if (newAccountName) { + dispatch(actions.setAccountLabel(newAccountAddress, newAccountName)) + } + return newAccountAddress + }) + }, + } +} + +function mergeProps (stateProps, dispatchProps) { + const { + onCreateNewAccount, + } = stateProps + const { + createAccount, + } = dispatchProps + + return { + ...stateProps, + ...dispatchProps, + onSave: (newAccountName) => { + return createAccount(newAccountName) + .then(newAccountAddress => onCreateNewAccount(newAccountAddress)) + }, + } +} + +export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(NewAccountModal) diff --git a/ui/app/components/app/modals/notification-modal.js b/ui/app/components/app/modals/notification-modal.js index a4282595f..800cd4f2f 100644 --- a/ui/app/components/app/modals/notification-modal.js +++ b/ui/app/components/app/modals/notification-modal.js @@ -1,8 +1,7 @@ -const { Component } = require('react') -const PropTypes = require('prop-types') -const h = require('react-hyperscript') -const connect = require('react-redux').connect -const actions = require('../../../store/actions') +import PropTypes from 'prop-types' +import React, {Component} from 'react' +import {connect} from 'react-redux' +import { hideModal } from '../../../store/actions' class NotificationModal extends Component { static contextProps = { @@ -23,41 +22,44 @@ class NotificationModal extends Component { const showButtons = showCancelButton || showConfirmButton - return h('div', [ - h('div.notification-modal__wrapper', { - }, [ - - h('div.notification-modal__header', {}, [ - this.context.t(header), - ]), - - h('div.notification-modal__message-wrapper', {}, [ - h('div.notification-modal__message', {}, [ - this.context.t(message), - ]), - ]), - - h('div.modal-close-x', { - onClick: hideModal, - }), - - showButtons && h('div.notification-modal__buttons', [ - - showCancelButton && h('div.btn-default.notification-modal__buttons__btn', { - onClick: hideModal, - }, t('cancel')), - - showConfirmButton && h('div.button.btn-secondary.notification-modal__buttons__btn', { - onClick: () => { - onConfirm() - hideModal() - }, - }, t('confirm')), - - ]), - - ]), - ]) + return ( +
    +
    +
    + {this.context.t(header)} +
    +
    +
    + {this.context.t(message)} +
    +
    +
    + {showButtons && ( +
    + {showCancelButton && ( +
    + {t('cancel')} +
    + )} + {showConfirmButton && ( +
    { + onConfirm() + hideModal() + }} + > + {t('confirm')} +
    + )} +
    + )} +
    +
    + ) } } @@ -74,7 +76,7 @@ NotificationModal.propTypes = { const mapDispatchToProps = dispatch => { return { hideModal: () => { - dispatch(actions.hideModal()) + dispatch(hideModal()) }, } } diff --git a/ui/app/components/app/multiple-notifications/multiple-notifications.component.js b/ui/app/components/app/multiple-notifications/multiple-notifications.component.js index f9f6fe887..59f540045 100644 --- a/ui/app/components/app/multiple-notifications/multiple-notifications.component.js +++ b/ui/app/components/app/multiple-notifications/multiple-notifications.component.js @@ -38,9 +38,13 @@ export default class MultipleNotifications extends PureComponent { className="home-notification-wrapper__i-container" onClick={() => this.setState({ showAll: !showAll })} > - {childrenToRender.length > 1 ? : null} + {childrenToRender.length > 1 ? ( + + ) : null}
    ) 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 bcac637bd..1ecec3179 100644 --- a/ui/app/components/app/network-display/network-display.component.js +++ b/ui/app/components/app/network-display/network-display.component.js @@ -38,13 +38,15 @@ export default class NetworkDisplay extends Component { return networkClass ?
    - :
    + : ( +
    + ) } render () { @@ -61,13 +63,15 @@ export default class NetworkDisplay extends Component { { networkClass ?
    - :
    + : ( +
    + ) }
    { type === 'rpc' && nickname ? nickname : this.context.t(type) } diff --git a/ui/app/components/app/network.js b/ui/app/components/app/network.js index d46906a66..88204aef7 100644 --- a/ui/app/components/app/network.js +++ b/ui/app/components/app/network.js @@ -1,7 +1,6 @@ -const Component = require('react').Component -const PropTypes = require('prop-types') -const h = require('react-hyperscript') -const connect = require('react-redux').connect +import PropTypes from 'prop-types' +import React, {Component} from 'react' + const classnames = require('classnames') const inherits = require('util').inherits const NetworkDropdownIcon = require('./dropdowns/components/network-dropdown-icon') @@ -10,8 +9,7 @@ Network.contextTypes = { t: PropTypes.func, } -module.exports = connect()(Network) - +module.exports = Network inherits(Network, Component) @@ -19,15 +17,14 @@ function Network () { Component.call(this) } -Network.prototype.render = function () { - const props = this.props +Network.prototype.render = function Network () { const context = this.context - const networkNumber = props.network + const networkNumber = this.props.network let providerName, providerNick, providerUrl try { - providerName = props.provider.type - providerNick = props.provider.nickname || '' - providerUrl = props.provider.rpcTarget + providerName = this.props.provider.type + providerNick = this.props.provider.nickname || '' + providerUrl = this.props.provider.rpcTarget } catch (e) { providerName = null } @@ -56,96 +53,124 @@ Network.prototype.render = function () { } return ( - h('div.network-component.pointer', { - className: classnames({ +
    { + })} + title={hoverText} + onClick={(event) => { if (!this.props.disabled) { this.props.onClick(event) } - }, - }, [ - (function () { + }} + > + {(function () { switch (iconName) { case 'ethereum-network': - return h('.network-indicator', [ - h(NetworkDropdownIcon, { - backgroundColor: '#038789', // $blue-lagoon - nonSelectBackgroundColor: '#15afb2', - loading: networkNumber === 'loading', - }), - h('.network-name', context.t('mainnet')), - h('.network-indicator__down-arrow'), - ]) + return ( +
    + +
    + {context.t('mainnet')} +
    +
    +
    + ) case 'ropsten-test-network': - return h('.network-indicator', [ - h(NetworkDropdownIcon, { - backgroundColor: '#e91550', // $crimson - nonSelectBackgroundColor: '#ec2c50', - loading: networkNumber === 'loading', - }), - h('.network-name', context.t('ropsten')), - h('.network-indicator__down-arrow'), - ]) + return ( +
    + +
    + {context.t('ropsten')} +
    +
    +
    + ) case 'kovan-test-network': - return h('.network-indicator', [ - h(NetworkDropdownIcon, { - backgroundColor: '#690496', // $purple - nonSelectBackgroundColor: '#b039f3', - loading: networkNumber === 'loading', - }), - h('.network-name', context.t('kovan')), - h('.network-indicator__down-arrow'), - ]) + return ( +
    + +
    + {context.t('kovan')} +
    +
    +
    + ) case 'rinkeby-test-network': - return h('.network-indicator', [ - h(NetworkDropdownIcon, { - backgroundColor: '#ebb33f', // $tulip-tree - nonSelectBackgroundColor: '#ecb23e', - loading: networkNumber === 'loading', - }), - h('.network-name', context.t('rinkeby')), - h('.network-indicator__down-arrow'), - ]) + return ( +
    + +
    + {context.t('rinkeby')} +
    +
    +
    + ) 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('.network-indicator__down-arrow'), - ]) + return ( +
    + +
    {context.t('goerli')}
    +
    +
    + ) default: - return h('.network-indicator', [ - networkNumber === 'loading' - ? h('span.pointer.network-loading-spinner', { - onClick: (event) => this.props.onClick(event), - }, [ - h('img', { - title: context.t('attemptingConnect'), - src: 'images/loading.svg', - }), - ]) - : h('i.fa.fa-question-circle.fa-lg', { - style: { - color: 'rgb(125, 128, 130)', - }, - }), - - h('.network-name', providerName === 'localhost' ? context.t('localhost') : providerNick || context.t('privateNetwork')), - h('.network-indicator__down-arrow'), - ]) + return ( +
    + {networkNumber === 'loading' + ? ( + this.props.onClick(event)} + > + + + ) + : ( + + )} +
    + { + providerName === 'localhost' + ? context.t('localhost') + : providerNick || context.t('privateNetwork') + } +
    +
    +
    + ) } - })(), - ]) + })()} +
    ) } diff --git a/ui/app/components/app/permission-page-container/index.js b/ui/app/components/app/permission-page-container/index.js new file mode 100644 index 000000000..ea3b8daaa --- /dev/null +++ b/ui/app/components/app/permission-page-container/index.js @@ -0,0 +1,3 @@ +export {default} from './permission-page-container.container' +export {default as PermissionPageContainerContent} from './permission-page-container-content' +export {default as PermissionPageContainerHeader} from './permission-page-container-header' diff --git a/ui/app/components/app/permission-page-container/index.scss b/ui/app/components/app/permission-page-container/index.scss new file mode 100644 index 000000000..7979867fa --- /dev/null +++ b/ui/app/components/app/permission-page-container/index.scss @@ -0,0 +1,281 @@ +.permission-approval-container { + display: flex; + border: none; + box-shadow: none; + margin-top: 45px; + width: 466px; + min-height: 468px; + + &__header { + display: flex; + flex-direction: column; + align-items: flex-end; + border-bottom: 1px solid $geyser; + padding: 9px; + } + + &__title { + @extend %header--18; + line-height: 25px; + text-align: center; + position: fixed; + left: 0; + width: 100%; + } + + &__content { + display: flex; + overflow-y: auto; + flex: 1; + flex-direction: column; + color: #7C808E; + + &--redirect { + margin-top: 60px; + } + + h1, h2 { + color: #4A4A4A; + display: flex; + justify-content: center; + text-align: center; + } + + h2 { + font-size: 16px; + line-height: 18px; + padding: 20px; + } + + h1 { + font-size: 22px; + line-height: 26px; + padding: 20px; + } + + p { + padding: 0 40px; + text-align: center; + font-size: 12px; + line-height: 18px; + } + + a, a:hover { + color: $dodger-blue; + } + + section { + h1 { + padding: 30px 0px 0px 0px; + } + + h2 { + padding: 0px 0px 20px 0px; + } + } + + &__requested { + text-align: left; + } + + &__revoke-note { + margin-top: 24px; + } + + &__checkbox { + margin-right: 10px; + } + + &__permission { + margin-top: 18px; + + i { + color: #6A737D; + } + label { + margin-left: 6px; + color: #24292E; + } + } + + .permission-approval-visual { + display: flex; + flex-direction: row; + justify-content: space-evenly; + position: relative; + margin: 0 32px; + margin-top: 40px; + + section { + display: flex; + flex-direction: column; + align-items: center; + flex: 1; + } + + h1 { + font-size: 14px; + line-height: 18px; + padding: 8px 0 0; + } + + h2 { + font-size: 12px; + line-height: 17px; + color: #6A737D; + padding: 0; + } + + &__check { + width: 40px; + height: 40px; + background: white url("/images/permissions-check.svg") no-repeat; + margin-top: 24px; + z-index: 1; + } + + &__reject { + background: white; + z-index: 1; + display: flex; + justify-content: center; + align-items: center; + + i { + color: #D73A49; + transform: scale(3); + } + } + + &__broken-line { + z-index: 0; + position: absolute; + top: 43px; + } + + &__identicon, .icon-with-fallback__identicon { + width: 32px; + height: 32px; + z-index: 1; + + &--default { + background-color: #777A87; + color: white; + width: 64px; + height: 64px; + border-radius: 32px; + display: flex; + align-items: center; + justify-content: center; + font-weight: bold; + z-index: 1; + } + } + + &__identicon-container, .icon-with-fallback__identicon-container { + padding: 1rem; + flex: 1; + position: relative; + width: 100%; + display: flex; + justify-content: center; + align-items: center; + } + + &__identicon-border, .icon-with-fallback__identicon-border { + height: 64px; + width: 64px; + border-radius: 50%; + border: 1px solid white; + position: absolute; + background: #FFFFFF; + box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.25); + } + + &:before { + border-top: 2px dashed #CDD1E4; + content: ""; + margin: 0 auto; + position: absolute; + top: 32px; + left: 0; + bottom: 0; + right: 0; + width: 65%; + z-index: -1; + } + + &__account-info { + display: flex; + flex-direction: column; + align-items: center; + + &__label { + @extend %content-text; + line-height: 20px; + color: #000000; + } + + &__address { + @extend %font; + font-size: 12px; + line-height: 17px; + color: #6A737D; + } + } + } + + .secure-badge { + display: flex; + justify-content: center; + padding: 25px; + } + } + + &__permissions-header { + @extend %content-text; + line-height: 20px; + color: #6A737D; + + &--redirect { + text-align: center; + } + } + + &__permissions-container { + display: flex; + flex-direction: column; + margin-top: 33px; + } + + .page-container__footer { + border-top: none; + align-items: center; + + header { + width: 300px; + } + } + + &__permissions-header-redirect { + text-align: center; + } + + @media screen and (max-width: 575px) { + width: 100%; + margin-top: 25px; + padding: 10px; + + &__title { + position: initial; + } + + &__content-approval-visual { + margin-top: 16px; + } + + .page-container__footer header { + padding: 0; + } + } +} diff --git a/ui/app/components/app/permission-page-container/permission-page-container-content/index.js b/ui/app/components/app/permission-page-container/permission-page-container-content/index.js new file mode 100644 index 000000000..899d168f9 --- /dev/null +++ b/ui/app/components/app/permission-page-container/permission-page-container-content/index.js @@ -0,0 +1 @@ +export {default} from './permission-page-container-content.component' diff --git a/ui/app/components/app/permission-page-container/permission-page-container-content/permission-page-container-content.component.js b/ui/app/components/app/permission-page-container/permission-page-container-content/permission-page-container-content.component.js new file mode 100644 index 000000000..d6a62dbbf --- /dev/null +++ b/ui/app/components/app/permission-page-container/permission-page-container-content/permission-page-container-content.component.js @@ -0,0 +1,163 @@ +import PropTypes from 'prop-types' +import React, { PureComponent } from 'react' +import Identicon from '../../../ui/identicon' +import IconWithFallBack from '../../../ui/icon-with-fallback' +import classnames from 'classnames' + +export default class PermissionPageContainerContent extends PureComponent { + + static propTypes = { + requestMetadata: PropTypes.object.isRequired, + domainMetadata: PropTypes.object.isRequired, + selectedPermissions: PropTypes.object.isRequired, + permissionsDescriptions: PropTypes.object.isRequired, + onPermissionToggle: PropTypes.func.isRequired, + selectedAccount: PropTypes.object, + redirect: PropTypes.bool, + permissionRejected: PropTypes.bool, + } + + static defaultProps = { + redirect: null, + permissionRejected: null, + selectedAccount: {}, + } + + static contextTypes = { + t: PropTypes.func, + } + + renderAccountInfo = (account) => { + return ( +
    +
    + { account.label } +
    +
    + { account.truncatedAddress } +
    +
    + ) + } + + renderPermissionApprovalVisual = () => { + const { + requestMetadata, domainMetadata, selectedAccount, redirect, permissionRejected, + } = this.props + + return ( +
    +
    + + { redirect ? null :

    {domainMetadata.name}

    } + { redirect ? null :

    {requestMetadata.origin}

    } +
    + { permissionRejected + ? + : + } + +
    +
    +
    + +
    + { redirect ? null : this.renderAccountInfo(selectedAccount) } +
    +
    + ) + } + + renderRequestedPermissions () { + const { + selectedPermissions, permissionsDescriptions, onPermissionToggle, + } = this.props + const { t } = this.context + + const items = Object.keys(selectedPermissions).map((methodName) => { + + // the request will almost certainly be reject by rpc-cap if this happens + if (!permissionsDescriptions[methodName]) { + console.warn(`Unknown permission requested: ${methodName}`) + } + const description = permissionsDescriptions[methodName] || methodName + // don't allow deselecting eth_accounts + const isDisabled = methodName === 'eth_accounts' + + return ( +
    { + if (!isDisabled) { + onPermissionToggle(methodName) + } + }} + > + { selectedPermissions[methodName] + ? + : + } + +
    + ) + }) + + return ( +
    + {items} +
    { t('revokeInPermissions') }
    +
    + ) + } + + render () { + const { domainMetadata, redirect, permissionRejected } = this.props + const { t } = this.context + + let titleArgs + if (redirect && permissionRejected) { + titleArgs = [ 'cancelledConnectionWithMetaMask' ] + } else if (redirect) { + titleArgs = [ 'connectingWithMetaMask' ] + } else if (domainMetadata.extensionId) { + titleArgs = [ 'externalExtension', [domainMetadata.extensionId] ] + } else { + titleArgs = [ 'likeToConnect', [domainMetadata.name] ] + } + + return ( +
    +
    + { t(...titleArgs) } +
    + {this.renderPermissionApprovalVisual()} + { !redirect + ? ( +
    +
    + { domainMetadata.extensionId + ? t('thisWillAllowExternalExtension', [domainMetadata.extensionId]) + : t('thisWillAllow', [domainMetadata.name]) + } +
    + { this.renderRequestedPermissions() } +
    + ) + : ( +
    + { t('redirectingBackToDapp') } +
    + ) + } +
    + ) + } +} diff --git a/ui/app/components/app/permission-page-container/permission-page-container-header/index.js b/ui/app/components/app/permission-page-container/permission-page-container-header/index.js new file mode 100644 index 000000000..45ef9036b --- /dev/null +++ b/ui/app/components/app/permission-page-container/permission-page-container-header/index.js @@ -0,0 +1 @@ +export {default} from './permission-page-container-header.component' diff --git a/ui/app/components/app/provider-page-container/provider-page-container-header/provider-page-container-header.component.js b/ui/app/components/app/permission-page-container/permission-page-container-header/permission-page-container-header.component.js similarity index 58% rename from ui/app/components/app/provider-page-container/provider-page-container-header/provider-page-container-header.component.js rename to ui/app/components/app/permission-page-container/permission-page-container-header/permission-page-container-header.component.js index 41bf6c3dd..8ba3444ba 100644 --- a/ui/app/components/app/provider-page-container/provider-page-container-header/provider-page-container-header.component.js +++ b/ui/app/components/app/permission-page-container/permission-page-container-header/permission-page-container-header.component.js @@ -1,10 +1,10 @@ import React, {PureComponent} from 'react' import NetworkDisplay from '../../network-display' -export default class ProviderPageContainerHeader extends PureComponent { +export default class PermissionPageContainerHeader extends PureComponent { render () { return ( -
    +
    ) diff --git a/ui/app/components/app/permission-page-container/permission-page-container.component.js b/ui/app/components/app/permission-page-container/permission-page-container.component.js new file mode 100644 index 000000000..b7cbcd6ba --- /dev/null +++ b/ui/app/components/app/permission-page-container/permission-page-container.component.js @@ -0,0 +1,151 @@ +import PropTypes from 'prop-types' +import React, { Component } from 'react' +import deepEqual from 'fast-deep-equal' +import { PermissionPageContainerContent } from '.' +import { PageContainerFooter } from '../../ui/page-container' + +export default class PermissionPageContainer extends Component { + + static propTypes = { + approvePermissionsRequest: PropTypes.func.isRequired, + rejectPermissionsRequest: PropTypes.func.isRequired, + selectedIdentity: PropTypes.object, + permissionsDescriptions: PropTypes.object.isRequired, + request: PropTypes.object, + redirect: PropTypes.bool, + permissionRejected: PropTypes.bool, + requestMetadata: PropTypes.object, + targetDomainMetadata: PropTypes.object.isRequired, + }; + + static defaultProps = { + redirect: null, + permissionRejected: null, + request: {}, + requestMetadata: {}, + selectedIdentity: {}, + }; + + static contextTypes = { + t: PropTypes.func, + metricsEvent: PropTypes.func, + }; + + state = { + selectedPermissions: this.getRequestedMethodState( + this.getRequestedMethodNames(this.props) + ), + } + + componentDidUpdate () { + const newMethodNames = this.getRequestedMethodNames(this.props) + + if (!deepEqual(Object.keys(this.state.selectedPermissions), newMethodNames)) { + // this should be a new request, so just overwrite + this.setState({ + selectedPermissions: this.getRequestedMethodState(newMethodNames), + }) + } + } + + getRequestedMethodState (methodNames) { + return methodNames.reduce( + (acc, methodName) => { + acc[methodName] = true + return acc + }, + {} + ) + } + + getRequestedMethodNames (props) { + return Object.keys(props.request.permissions || {}) + } + + onPermissionToggle = methodName => { + this.setState({ + selectedPermissions: { + ...this.state.selectedPermissions, + [methodName]: !this.state.selectedPermissions[methodName], + }, + }) + } + + componentDidMount () { + this.context.metricsEvent({ + eventOpts: { + category: 'Auth', + action: 'Connect', + name: 'Tab Opened', + }, + }) + } + + onCancel = () => { + const { request, rejectPermissionsRequest } = this.props + rejectPermissionsRequest(request.metadata.id) + } + + onSubmit = () => { + const { + request: _request, approvePermissionsRequest, rejectPermissionsRequest, selectedIdentity, + } = this.props + + const request = { + ..._request, + permissions: { ..._request.permissions }, + } + + Object.keys(this.state.selectedPermissions).forEach(key => { + if (!this.state.selectedPermissions[key]) { + delete request.permissions[key] + } + }) + + if (Object.keys(request.permissions).length > 0) { + approvePermissionsRequest(request, [selectedIdentity.address]) + } else { + rejectPermissionsRequest(request.metadata.id) + } + } + + render () { + const { + requestMetadata, + targetDomainMetadata, + permissionsDescriptions, + selectedIdentity, + redirect, + permissionRejected, + } = this.props + + return ( +
    + + { !redirect + ? ( + this.onCancel()} + cancelText={this.context.t('cancel')} + onSubmit={() => this.onSubmit()} + submitText={this.context.t('submit')} + submitButtonType="confirm" + buttonSizeLarge={false} + /> + ) + : null + } +
    + ) + } +} diff --git a/ui/app/components/app/permission-page-container/permission-page-container.container.js b/ui/app/components/app/permission-page-container/permission-page-container.container.js new file mode 100644 index 000000000..f83393c70 --- /dev/null +++ b/ui/app/components/app/permission-page-container/permission-page-container.container.js @@ -0,0 +1,28 @@ +import { connect } from 'react-redux' +import { compose } from 'recompose' +import { withRouter } from 'react-router-dom' +import PermissionPageContainer from './permission-page-container.component' +import { + getPermissionsDescriptions, + getDomainMetadata, +} from '../../../selectors/selectors' + +const mapStateToProps = (state, ownProps) => { + const { request, cachedOrigin } = ownProps + const { metadata: requestMetadata = {} } = request || {} + + const domainMetadata = getDomainMetadata(state) + const origin = requestMetadata.origin || cachedOrigin + const targetDomainMetadata = (domainMetadata[origin] || { name: origin, icon: null }) + + return { + permissionsDescriptions: getPermissionsDescriptions(state), + requestMetadata, + targetDomainMetadata, + } +} + +export default compose( + withRouter, + connect(mapStateToProps) +)(PermissionPageContainer) diff --git a/ui/app/components/app/provider-page-container/index.js b/ui/app/components/app/provider-page-container/index.js deleted file mode 100644 index 927c35940..000000000 --- a/ui/app/components/app/provider-page-container/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export {default} from './provider-page-container.component' -export {default as ProviderPageContainerContent} from './provider-page-container-content' -export {default as ProviderPageContainerHeader} from './provider-page-container-header' diff --git a/ui/app/components/app/provider-page-container/index.scss b/ui/app/components/app/provider-page-container/index.scss deleted file mode 100644 index 8d35ac179..000000000 --- a/ui/app/components/app/provider-page-container/index.scss +++ /dev/null @@ -1,121 +0,0 @@ -.provider-approval-container { - display: flex; - - &__header { - display: flex; - flex-direction: column; - align-items: flex-end; - border-bottom: 1px solid $geyser; - padding: 9px; - } - - &__content { - display: flex; - overflow-y: auto; - flex: 1; - flex-direction: column; - justify-content: space-between; - color: #7C808E; - - h1, h2 { - color: #4A4A4A; - display: flex; - justify-content: center; - text-align: center; - } - - h2 { - font-size: 16px; - line-height: 18px; - padding: 20px; - } - - h1 { - font-size: 22px; - line-height: 26px; - padding: 20px; - } - - p { - padding: 0 40px; - text-align: center; - font-size: 12px; - line-height: 18px; - } - - a, a:hover { - color: $dodger-blue; - } - - .provider-approval-visual { - display: flex; - flex-direction: row; - justify-content: space-evenly; - position: relative; - margin: 0 32px; - - section { - display: flex; - flex-direction: column; - align-items: center; - flex: 1; - } - - h1 { - font-size: 14px; - line-height: 18px; - padding: 8px 0 0; - } - - h2 { - font-size: 10px; - line-height: 14px; - padding: 0; - color: #A2A4AC; - } - - &__check { - width: 40px; - height: 40px; - background: white url("/images/provider-approval-check.svg") no-repeat; - margin-top: 14px; - } - - &__identicon { - width: 64px; - height: 64px; - - &--default { - background-color: #777A87; - color: white; - width: 64px; - height: 64px; - border-radius: 32px; - display: flex; - align-items: center; - justify-content: center; - font-weight: bold; - } - } - - &:before { - border-top: 2px dashed #CDD1E4; - content: ""; - margin: 0 auto; - position: absolute; - top: 32px; - left: 0; - bottom: 0; - right: 0; - width: 65%; - z-index: -1; - } - } - - .secure-badge { - display: flex; - justify-content: center; - padding: 25px; - } - } -} diff --git a/ui/app/components/app/provider-page-container/provider-page-container-content/index.js b/ui/app/components/app/provider-page-container/provider-page-container-content/index.js deleted file mode 100644 index 73e491adc..000000000 --- a/ui/app/components/app/provider-page-container/provider-page-container-content/index.js +++ /dev/null @@ -1 +0,0 @@ -export {default} from './provider-page-container-content.container' diff --git a/ui/app/components/app/provider-page-container/provider-page-container-content/provider-page-container-content.component.js b/ui/app/components/app/provider-page-container/provider-page-container-content/provider-page-container-content.component.js deleted file mode 100644 index 4062b130f..000000000 --- a/ui/app/components/app/provider-page-container/provider-page-container-content/provider-page-container-content.component.js +++ /dev/null @@ -1,87 +0,0 @@ -import PropTypes from 'prop-types' -import React, {PureComponent} from 'react' -import Identicon from '../../../ui/identicon' - -export default class ProviderPageContainerContent extends PureComponent { - static propTypes = { - origin: PropTypes.string.isRequired, - selectedIdentity: PropTypes.object.isRequired, - siteImage: PropTypes.string, - siteTitle: PropTypes.string, - hostname: PropTypes.string, - extensionId: PropTypes.string, - } - - static contextTypes = { - t: PropTypes.func, - }; - - renderConnectVisual = (title, identifier) => { - const { selectedIdentity, siteImage } = this.props - - return ( -
    -
    - {siteImage ? ( - - ) : ( - - {title.charAt(0).toUpperCase()} - - )} -

    {title}

    -

    {identifier}

    -
    - -
    - -

    {selectedIdentity.name}

    -
    -
    - ) - } - - render () { - const { siteTitle, hostname, extensionId } = this.props - const { t } = this.context - - const title = extensionId ? - 'External Extension' : - siteTitle || hostname - - const identifier = extensionId ? - `Extension ID: '${extensionId}'` : - hostname - - return ( -
    -
    -

    {t('connectRequest')}

    - {this.renderConnectVisual(title, identifier)} -

    {t('providerRequest', [title])}

    -

    - {t('providerRequestInfo')} -
    - - {t('learnMore')}. - -

    -
    -
    - -
    -
    - ) - } -} diff --git a/ui/app/components/app/provider-page-container/provider-page-container-content/provider-page-container-content.container.js b/ui/app/components/app/provider-page-container/provider-page-container-content/provider-page-container-content.container.js deleted file mode 100644 index 4dbdddd16..000000000 --- a/ui/app/components/app/provider-page-container/provider-page-container-content/provider-page-container-content.container.js +++ /dev/null @@ -1,11 +0,0 @@ -import { connect } from 'react-redux' -import ProviderPageContainerContent from './provider-page-container-content.component' -import { getSelectedIdentity } from '../../../../selectors/selectors' - -const mapStateToProps = (state) => { - return { - selectedIdentity: getSelectedIdentity(state), - } -} - -export default connect(mapStateToProps)(ProviderPageContainerContent) diff --git a/ui/app/components/app/provider-page-container/provider-page-container-header/index.js b/ui/app/components/app/provider-page-container/provider-page-container-header/index.js deleted file mode 100644 index 430627d3a..000000000 --- a/ui/app/components/app/provider-page-container/provider-page-container-header/index.js +++ /dev/null @@ -1 +0,0 @@ -export {default} from './provider-page-container-header.component' 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 deleted file mode 100644 index 7d152e4cb..000000000 --- a/ui/app/components/app/provider-page-container/provider-page-container.component.js +++ /dev/null @@ -1,107 +0,0 @@ -import PropTypes from 'prop-types' -import React, {PureComponent} from 'react' -import { ProviderPageContainerContent, ProviderPageContainerHeader } from '.' -import { PageContainerFooter } from '../../ui/page-container' -import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../../../app/scripts/lib/enums' -import { getEnvironmentType } from '../../../../../app/scripts/lib/util' - -export default class ProviderPageContainer extends PureComponent { - static propTypes = { - approveProviderRequestByOrigin: PropTypes.func.isRequired, - rejectProviderRequestByOrigin: PropTypes.func.isRequired, - origin: PropTypes.string.isRequired, - siteImage: PropTypes.string, - siteTitle: PropTypes.string, - hostname: PropTypes.string, - extensionId: PropTypes.string, - }; - - static contextTypes = { - t: PropTypes.func, - metricsEvent: PropTypes.func, - }; - - componentDidMount () { - if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION) { - window.addEventListener('beforeunload', this._beforeUnload) - } - this.context.metricsEvent({ - eventOpts: { - category: 'Auth', - action: 'Connect', - name: 'Popup Opened', - }, - }) - } - - _beforeUnload = () => { - const { origin, rejectProviderRequestByOrigin } = this.props - this.context.metricsEvent({ - eventOpts: { - category: 'Auth', - action: 'Connect', - name: 'Cancel Connect Request Via Notification Close', - }, - }) - this._removeBeforeUnload() - rejectProviderRequestByOrigin(origin) - } - - _removeBeforeUnload () { - window.removeEventListener('beforeunload', this._beforeUnload) - } - - componentWillUnmount () { - this._removeBeforeUnload() - } - - onCancel = () => { - const { origin, rejectProviderRequestByOrigin } = this.props - this.context.metricsEvent({ - eventOpts: { - category: 'Auth', - action: 'Connect', - name: 'Canceled', - }, - }) - this._removeBeforeUnload() - rejectProviderRequestByOrigin(origin) - } - - onSubmit = () => { - const { approveProviderRequestByOrigin, origin } = this.props - this.context.metricsEvent({ - eventOpts: { - category: 'Auth', - action: 'Connect', - name: 'Confirmed', - }, - }) - this._removeBeforeUnload() - approveProviderRequestByOrigin(origin) - } - - render () { - const {origin, siteImage, siteTitle, hostname, extensionId} = this.props - - return ( -
    - - - this.onCancel()} - cancelText={this.context.t('cancel')} - onSubmit={() => this.onSubmit()} - submitText={this.context.t('connect')} - submitButtonType="confirm" - /> -
    - ) - } -} diff --git a/ui/app/components/app/selected-account/tests/selected-account-component.test.js b/ui/app/components/app/selected-account/tests/selected-account-component.test.js index 78a94d1c8..42e5c8f1b 100644 --- a/ui/app/components/app/selected-account/tests/selected-account-component.test.js +++ b/ui/app/components/app/selected-account/tests/selected-account-component.test.js @@ -5,10 +5,12 @@ import SelectedAccount from '../selected-account.component' describe('SelectedAccount Component', () => { it('should render checksummed address', () => { - const wrapper = render(, { context: { t: () => {}}}) + const wrapper = render(( + + ), { context: { t: () => {}}}) // Checksummed version of address is displayed assert.equal(wrapper.find('.selected-account__address').text(), '0x1B82...5C9D') assert.equal(wrapper.find('.selected-account__name').text(), 'testName') diff --git a/ui/app/components/app/shift-list-item.js b/ui/app/components/app/shift-list-item.js deleted file mode 100644 index f5fa00047..000000000 --- a/ui/app/components/app/shift-list-item.js +++ /dev/null @@ -1,204 +0,0 @@ -const inherits = require('util').inherits -const Component = require('react').Component -const PropTypes = require('prop-types') -const h = require('react-hyperscript') -const connect = require('react-redux').connect -const explorerLink = require('etherscan-link').createExplorerLink -const actions = require('../../store/actions') -const { formatDate, addressSummary } = require('../../helpers/utils/util') - -const CopyButton = require('../ui/copyButton') -const EthBalance = require('../ui/eth-balance') -const Tooltip = require('../ui/tooltip') - - -ShiftListItem.contextTypes = { - t: PropTypes.func, -} - -module.exports = connect(mapStateToProps)(ShiftListItem) - - -function mapStateToProps (state) { - return { - selectedAddress: state.metamask.selectedAddress, - conversionRate: state.metamask.conversionRate, - currentCurrency: state.metamask.currentCurrency, - } -} - -inherits(ShiftListItem, Component) - -function ShiftListItem () { - Component.call(this) -} - -ShiftListItem.prototype.render = function () { - return h('div.transaction-list-item.tx-list-clickable', { - style: { - paddingTop: '20px', - paddingBottom: '20px', - justifyContent: 'space-around', - alignItems: 'center', - flexDirection: 'row', - }, - }, [ - h('div', { - style: { - width: '0px', - position: 'relative', - bottom: '19px', - }, - }, [ - h('img', { - src: 'https://shapeshift.io/logo.png', - style: { - height: '35px', - width: '132px', - position: 'absolute', - clip: 'rect(0px,30px,34px,0px)', - }, - }), - ]), - - this.renderInfo(), - this.renderUtilComponents(), - ]) -} - -ShiftListItem.prototype.renderUtilComponents = function () { - var props = this.props - const { conversionRate, currentCurrency } = props - - switch (props.response.status) { - case 'no_deposits': - return h('.flex-row', [ - h(CopyButton, { - value: this.props.depositAddress, - }), - h(Tooltip, { - title: this.context.t('qrCode'), - }, [ - h('i.fa.fa-qrcode.pointer.pop-hover', { - onClick: () => props.dispatch(actions.reshowQrCode(props.depositAddress, props.depositType)), - style: { - margin: '5px', - marginLeft: '23px', - marginRight: '12px', - fontSize: '20px', - color: '#F7861C', - }, - }), - ]), - ]) - case 'received': - return h('.flex-row') - - case 'complete': - return h('.flex-row', [ - h(CopyButton, { - value: this.props.response.transaction, - }), - h(EthBalance, { - value: `${props.response.outgoingCoin}`, - conversionRate, - currentCurrency, - width: '55px', - shorten: true, - needsParse: false, - incoming: true, - style: { - fontSize: '15px', - color: '#01888C', - }, - }), - ]) - - case 'failed': - return '' - default: - return '' - } -} - -ShiftListItem.prototype.renderInfo = function () { - var props = this.props - switch (props.response.status) { - case 'no_deposits': - return h('.flex-column', { - style: { - overflow: 'hidden', - }, - }, [ - h('div', { - style: { - fontSize: 'x-small', - color: '#ABA9AA', - width: '100%', - }, - }, this.context.t('toETHviaShapeShift', [props.depositType])), - h('div', this.context.t('noDeposits')), - h('div', { - style: { - fontSize: 'x-small', - color: '#ABA9AA', - width: '100%', - }, - }, formatDate(props.time)), - ]) - case 'received': - return h('.flex-column', { - style: { - width: '200px', - overflow: 'hidden', - }, - }, [ - h('div', { - style: { - fontSize: 'x-small', - color: '#ABA9AA', - width: '100%', - }, - }, this.context.t('toETHviaShapeShift', [props.depositType])), - h('div', this.context.t('conversionProgress')), - h('div', { - style: { - fontSize: 'x-small', - color: '#ABA9AA', - width: '100%', - }, - }, formatDate(props.time)), - ]) - case 'complete': - var url = explorerLink(props.response.transaction, parseInt('1')) - - return h('.flex-column.pointer', { - style: { - width: '200px', - overflow: 'hidden', - }, - onClick: () => global.platform.openWindow({ url }), - }, [ - h('div', { - style: { - fontSize: 'x-small', - color: '#ABA9AA', - width: '100%', - }, - }, this.context.t('fromShapeShift')), - h('div', formatDate(props.time)), - h('div', { - style: { - fontSize: 'x-small', - color: '#ABA9AA', - width: '100%', - }, - }, addressSummary(props.response.transaction)), - ]) - - case 'failed': - return h('span.error', '(' + this.context.t('failed') + ')') - default: - return '' - } -} diff --git a/ui/app/components/app/shift-list-item/index.js b/ui/app/components/app/shift-list-item/index.js new file mode 100644 index 000000000..fd6796983 --- /dev/null +++ b/ui/app/components/app/shift-list-item/index.js @@ -0,0 +1 @@ +export { default } from './shift-list-item.container' diff --git a/ui/app/components/app/shift-list-item/shift-list-item.component.js b/ui/app/components/app/shift-list-item/shift-list-item.component.js new file mode 100644 index 000000000..5e8797d27 --- /dev/null +++ b/ui/app/components/app/shift-list-item/shift-list-item.component.js @@ -0,0 +1,240 @@ +import PropTypes from 'prop-types' +import React, { Component } from 'react' +const explorerLink = require('etherscan-link').createExplorerLink +const actions = require('../../../store/actions') +const { formatDate, addressSummary } = require('../../../helpers/utils/util') + +const CopyButton = require('../../ui/copyButton') +const EthBalance = require('../../ui/eth-balance').default +const Tooltip = require('../../ui/tooltip') + +export default class ShiftListItem extends Component { + static contextTypes = { + t: PropTypes.func, + } + + static defaultProps = { + conversionRate: undefined, + currentCurrency: undefined, + } + + static propTypes = { + depositType: PropTypes.string.isRequired, + dispatch: PropTypes.func.isRequired, + depositAddress: PropTypes.string.isRequired, + conversionRate: PropTypes.any, + currentCurrency: PropTypes.any, + time: PropTypes.string.isRequired, + response: PropTypes.shape({ + outgoingCoin: PropTypes.number.isRequired, + status: PropTypes.string.isRequired, + transaction: PropTypes.string.isRequired, + }), + } + + renderUtilComponents () { + const { conversionRate, currentCurrency } = this.props + + switch (this.props.response.status) { + case 'no_deposits': + return ( +
    + + + { + this.props.dispatch(actions.reshowQrCode(this.props.depositAddress, this.props.depositType)) + }} + style={{ + margin: '5px', + marginLeft: '23px', + marginRight: '12px', + fontSize: '20px', + color: '#F7861C', + }} + /> + +
    + ) + case 'received': + return
    + + case 'complete': + return ( +
    + + +
    + ) + + case 'failed': + return '' + + default: + return '' + } + } + + renderInfo () { + switch (this.props.response.status) { + case 'no_deposits': + return ( +
    +
    + {this.context.t('toETHviaShapeShift', [this.props.depositType])} +
    +
    + {this.context.t('noDeposits')} +
    +
    + {formatDate(this.props.time)} +
    +
    + ) + + case 'received': + return ( +
    +
    + {this.context.t('toETHviaShapeShift', [this.props.depositType])} +
    +
    + {this.context.t('conversionProgress')} +
    +
    + {formatDate(this.props.time)} +
    +
    + ) + + case 'complete': + const url = explorerLink(this.props.response.transaction, parseInt('1')) + return ( +
    global.platform.openWindow({ url })} + > +
    + {this.context.t('fromShapeShift')} +
    +
    + {formatDate(this.props.time)} +
    +
    + {addressSummary(this.props.response.transaction)} +
    +
    + ) + + case 'failed': + return ( + + {`(${this.context.t('failed')})`} + + ) + + default: + return '' + } + } + + render () { + return ( +
    +
    + +
    + {this.renderInfo()} + {this.renderUtilComponents()} +
    + ) + } +} diff --git a/ui/app/components/app/shift-list-item/shift-list-item.container.js b/ui/app/components/app/shift-list-item/shift-list-item.container.js new file mode 100644 index 000000000..1d7645dfd --- /dev/null +++ b/ui/app/components/app/shift-list-item/shift-list-item.container.js @@ -0,0 +1,12 @@ +import {connect} from 'react-redux' +import ShiftListItem from './shift-list-item.component' + +function mapStateToProps (state) { + return { + selectedAddress: state.metamask.selectedAddress, + conversionRate: state.metamask.conversionRate, + currentCurrency: state.metamask.currentCurrency, + } +} + +export default connect(mapStateToProps)(ShiftListItem) diff --git a/ui/app/components/app/sidebars/sidebar.component.js b/ui/app/components/app/sidebars/sidebar.component.js index 484b87e4b..6437675c9 100644 --- a/ui/app/components/app/sidebars/sidebar.component.js +++ b/ui/app/components/app/sidebars/sidebar.component.js @@ -20,13 +20,15 @@ export default class Sidebar extends Component { renderOverlay () { const { onOverlayClose } = this.props - return
    { - onOverlayClose && onOverlayClose() - this.props.hideSidebar() - } - } /> + return ( +
    { + onOverlayClose && onOverlayClose() + this.props.hideSidebar() + }} + /> + ) } renderSidebarContent () { diff --git a/ui/app/components/app/sidebars/tests/sidebars-component.test.js b/ui/app/components/app/sidebars/tests/sidebars-component.test.js index 5f6657dde..3416ad8f6 100644 --- a/ui/app/components/app/sidebars/tests/sidebars-component.test.js +++ b/ui/app/components/app/sidebars/tests/sidebars-component.test.js @@ -16,12 +16,14 @@ describe('Sidebar Component', function () { let wrapper beforeEach(() => { - wrapper = shallow() + wrapper = shallow(( + + )) }) afterEach(() => { diff --git a/ui/app/components/app/signature-request-original.js b/ui/app/components/app/signature-request-original.js deleted file mode 100644 index e23d724cb..000000000 --- a/ui/app/components/app/signature-request-original.js +++ /dev/null @@ -1,357 +0,0 @@ -const Component = require('react').Component -const PropTypes = require('prop-types') -const h = require('react-hyperscript') -const inherits = require('util').inherits -import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../../app/scripts/lib/enums' -import { getEnvironmentType } from '../../../../app/scripts/lib/util' -import Identicon from '../ui/identicon' -const connect = require('react-redux').connect -const ethUtil = require('ethereumjs-util') -const classnames = require('classnames') -const { compose } = require('recompose') -const { withRouter } = require('react-router-dom') -const { ObjectInspector } = require('react-inspector') - -import AccountListItem from '../../pages/send/account-list-item/account-list-item.component' - -const actions = require('../../store/actions') -const { conversionUtil } = require('../../helpers/utils/conversion-util') - -const { - getSelectedAccount, - getCurrentAccountWithSendEtherInfo, - getSelectedAddress, - conversionRateSelector, -} = require('../../selectors/selectors.js') - -import { clearConfirmTransaction } from '../../ducks/confirm-transaction/confirm-transaction.duck' -import Button from '../ui/button' - -const { DEFAULT_ROUTE } = require('../../helpers/constants/routes') - -function mapStateToProps (state) { - return { - balance: getSelectedAccount(state).balance, - selectedAccount: getCurrentAccountWithSendEtherInfo(state), - selectedAddress: getSelectedAddress(state), - requester: null, - requesterAddress: null, - conversionRate: conversionRateSelector(state), - } -} - -function mapDispatchToProps (dispatch) { - return { - goHome: () => dispatch(actions.goHome()), - clearConfirmTransaction: () => dispatch(clearConfirmTransaction()), - } -} - -function mergeProps (stateProps, dispatchProps, ownProps) { - const { - signPersonalMessage, - signTypedMessage, - cancelPersonalMessage, - cancelTypedMessage, - signMessage, - cancelMessage, - txData, - } = ownProps - - const { type } = txData - - let cancel - let sign - if (type === 'personal_sign') { - cancel = cancelPersonalMessage - sign = signPersonalMessage - } else if (type === 'eth_signTypedData') { - cancel = cancelTypedMessage - sign = signTypedMessage - } else if (type === 'eth_sign') { - cancel = cancelMessage - sign = signMessage - } - - return { - ...ownProps, - ...stateProps, - ...dispatchProps, - txData, - cancel, - sign, - } -} - -SignatureRequest.contextTypes = { - t: PropTypes.func, - metricsEvent: PropTypes.func, -} - -module.exports = compose( - withRouter, - connect(mapStateToProps, mapDispatchToProps, mergeProps) -)(SignatureRequest) - - -inherits(SignatureRequest, Component) -function SignatureRequest (props) { - Component.call(this) - - this.state = { - selectedAccount: props.selectedAccount, - } - this._beforeUnload = this._beforeUnload.bind(this) -} - -SignatureRequest.prototype._beforeUnload = function (event) { - const { clearConfirmTransaction, cancel } = this.props - const { metricsEvent } = this.context - metricsEvent({ - eventOpts: { - category: 'Transactions', - action: 'Sign Request', - name: 'Cancel Sig Request Via Notification Close', - }, - }) - clearConfirmTransaction() - cancel(event) -} - -SignatureRequest.prototype._removeBeforeUnload = function () { - if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION) { - window.removeEventListener('beforeunload', this._beforeUnload) - } -} - -SignatureRequest.prototype.componentDidMount = function () { - if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION) { - window.addEventListener('beforeunload', this._beforeUnload) - } -} - -SignatureRequest.prototype.componentWillUnmount = function () { - this._removeBeforeUnload() -} - -SignatureRequest.prototype.renderHeader = function () { - return h('div.request-signature__header', [ - - h('div.request-signature__header-background'), - - h('div.request-signature__header__text', this.context.t('sigRequest')), - - h('div.request-signature__header__tip-container', [ - h('div.request-signature__header__tip'), - ]), - - ]) -} - -SignatureRequest.prototype.renderAccount = function () { - const { selectedAccount } = this.state - - return h('div.request-signature__account', [ - - h('div.request-signature__account-text', [this.context.t('account') + ':']), - - h('div.request-signature__account-item', [ - h(AccountListItem, { - account: selectedAccount, - displayBalance: false, - }), - ]), - ]) -} - -SignatureRequest.prototype.renderBalance = function () { - const { balance, conversionRate } = this.props - - const balanceInEther = conversionUtil(balance, { - fromNumericBase: 'hex', - toNumericBase: 'dec', - fromDenomination: 'WEI', - numberOfDecimals: 6, - conversionRate, - }) - - return h('div.request-signature__balance', [ - - h('div.request-signature__balance-text', `${this.context.t('balance')}:`), - - h('div.request-signature__balance-value', `${balanceInEther} ETH`), - - ]) -} - -SignatureRequest.prototype.renderAccountInfo = function () { - return h('div.request-signature__account-info', [ - - this.renderAccount(), - - this.renderRequestIcon(), - - this.renderBalance(), - - ]) -} - -SignatureRequest.prototype.renderRequestIcon = function () { - const { requesterAddress } = this.props - - return h('div.request-signature__request-icon', [ - h(Identicon, { - diameter: 40, - address: requesterAddress, - }), - ]) -} - -SignatureRequest.prototype.renderRequestInfo = function () { - return h('div.request-signature__request-info', [ - - h('div.request-signature__headline', [ - this.context.t('yourSigRequested'), - ]), - - ]) -} - -SignatureRequest.prototype.msgHexToText = function (hex) { - try { - const stripped = ethUtil.stripHexPrefix(hex) - const buff = Buffer.from(stripped, 'hex') - return buff.length === 32 ? hex : buff.toString('utf8') - } catch (e) { - return hex - } -} - -// eslint-disable-next-line react/display-name -SignatureRequest.prototype.renderTypedData = function (data) { - const { domain, message } = JSON.parse(data) - return [ - h('div.request-signature__typed-container', [ - domain ? h('div', [ - h('h1', 'Domain'), - h(ObjectInspector, { data: domain, expandLevel: 1, name: 'domain' }), - ]) : '', - message ? h('div', [ - h('h1', 'Message'), - h(ObjectInspector, { data: message, expandLevel: 1, name: 'message' }), - ]) : '', - ]), - ] -} - -SignatureRequest.prototype.renderBody = function () { - let rows - let notice = this.context.t('youSign') + ':' - - const { txData } = this.props - const { type, msgParams: { data } } = txData - - if (type === 'personal_sign') { - rows = [{ name: this.context.t('message'), value: this.msgHexToText(data) }] - } else if (type === 'eth_signTypedData') { - rows = data - } else if (type === 'eth_sign') { - rows = [{ name: this.context.t('message'), value: data }] - notice = [this.context.t('signNotice'), - h('span.request-signature__help-link', { - onClick: () => { - global.platform.openWindow({ - url: 'https://metamask.zendesk.com/hc/en-us/articles/360015488751', - }) - }, - }, this.context.t('learnMore'))] - } - - return h('div.request-signature__body', {}, [ - - this.renderAccountInfo(), - - this.renderRequestInfo(), - - h('div.request-signature__notice', { - className: classnames({ - 'request-signature__notice': type === 'personal_sign' || type === 'eth_signTypedData', - 'request-signature__warning': type === 'eth_sign', - }), - }, [notice]), - - h('div.request-signature__rows', - rows.map(({ name, value }, index) => { - if (typeof value === 'boolean') { - value = value.toString() - } - return h('div.request-signature__row', { key: `request-signature-row-${index}` }, [ - h('div.request-signature__row-title', [`${name}:`]), - h('div.request-signature__row-value', value), - ]) - }) - ), - ]) -} - -SignatureRequest.prototype.renderFooter = function () { - const { cancel, sign } = this.props - - return h('div.request-signature__footer', [ - h(Button, { - type: 'default', - large: true, - className: 'request-signature__footer__cancel-button', - onClick: event => { - this._removeBeforeUnload() - cancel(event).then(() => { - this.context.metricsEvent({ - eventOpts: { - category: 'Transactions', - action: 'Sign Request', - name: 'Cancel', - }, - }) - this.props.clearConfirmTransaction() - this.props.history.push(DEFAULT_ROUTE) - }) - }, - }, this.context.t('cancel')), - h(Button, { - type: 'secondary', - large: true, - className: 'request-signature__footer__sign-button', - onClick: event => { - this._removeBeforeUnload() - sign(event).then(() => { - this.context.metricsEvent({ - eventOpts: { - category: 'Transactions', - action: 'Sign Request', - name: 'Confirm', - }, - }) - this.props.clearConfirmTransaction() - this.props.history.push(DEFAULT_ROUTE) - }) - }, - }, this.context.t('sign')), - ]) -} - -SignatureRequest.prototype.render = function () { - return ( - - h('div.request-signature__container', [ - - this.renderHeader(), - - this.renderBody(), - - this.renderFooter(), - - ]) - - ) - -} diff --git a/ui/app/components/app/signature-request-original/index.js b/ui/app/components/app/signature-request-original/index.js new file mode 100644 index 000000000..00a906785 --- /dev/null +++ b/ui/app/components/app/signature-request-original/index.js @@ -0,0 +1 @@ +export { default } from './signature-request-original.container' diff --git a/ui/app/components/app/signature-request-original/signature-request-original.component.js b/ui/app/components/app/signature-request-original/signature-request-original.component.js new file mode 100644 index 000000000..9a92ba5fb --- /dev/null +++ b/ui/app/components/app/signature-request-original/signature-request-original.component.js @@ -0,0 +1,324 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import ethUtil from 'ethereumjs-util' +import classnames from 'classnames' +import { ObjectInspector } from 'react-inspector' + +import { ENVIRONMENT_TYPE_NOTIFICATION } from '../../../../../app/scripts/lib/enums' +import { getEnvironmentType } from '../../../../../app/scripts/lib/util' +import Identicon from '../../ui/identicon' +import AccountListItem from '../../../pages/send/account-list-item/account-list-item.component' +import { conversionUtil } from '../../../helpers/utils/conversion-util' +import Button from '../../ui/button' +import { DEFAULT_ROUTE } from '../../../helpers/constants/routes' + +export default class SignatureRequestOriginal extends Component { + static contextTypes = { + t: PropTypes.func.isRequired, + metricsEvent: PropTypes.func.isRequired, + } + + static propTypes = { + balance: PropTypes.string, + cancel: PropTypes.func.isRequired, + clearConfirmTransaction: PropTypes.func.isRequired, + conversionRate: PropTypes.number, + history: PropTypes.object.isRequired, + requesterAddress: PropTypes.string, + selectedAccount: PropTypes.string, + sign: PropTypes.func.isRequired, + txData: PropTypes.object.isRequired, + } + + state = { + selectedAccount: this.props.selectedAccount, + } + + componentDidMount = () => { + if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION) { + window.addEventListener('beforeunload', this._beforeUnload) + } + } + + componentWillUnmount = () => { + this._removeBeforeUnload() + } + + _beforeUnload = (event) => { + const { clearConfirmTransaction, cancel } = this.props + const { metricsEvent } = this.context + metricsEvent({ + eventOpts: { + category: 'Transactions', + action: 'Sign Request', + name: 'Cancel Sig Request Via Notification Close', + }, + }) + clearConfirmTransaction() + cancel(event) + } + + _removeBeforeUnload = () => { + if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION) { + window.removeEventListener('beforeunload', this._beforeUnload) + } + } + + renderHeader = () => { + return ( +
    +
    + +
    + { this.context.t('sigRequest') } +
    + +
    +
    +
    +
    + ) + } + + renderAccount = () => { + const { selectedAccount } = this.state + + return ( +
    +
    + { `${this.context.t('account')}:` } +
    + +
    + +
    +
    + ) + } + + renderBalance = () => { + const { balance, conversionRate } = this.props + + const balanceInEther = conversionUtil(balance, { + fromNumericBase: 'hex', + toNumericBase: 'dec', + fromDenomination: 'WEI', + numberOfDecimals: 6, + conversionRate, + }) + + return ( +
    +
    + { `${this.context.t('balance')}:` } +
    +
    + { `${balanceInEther} ETH` } +
    +
    + ) + } + + renderRequestIcon = () => { + const { requesterAddress } = this.props + + return ( +
    + +
    + ) + } + + renderAccountInfo = () => { + return ( +
    + { this.renderAccount() } + { this.renderRequestIcon() } + { this.renderBalance() } +
    + ) + } + + renderRequestInfo = () => { + return ( +
    +
    + { this.context.t('yourSigRequested') } +
    +
    + ) + } + + msgHexToText = (hex) => { + try { + const stripped = ethUtil.stripHexPrefix(hex) + const buff = Buffer.from(stripped, 'hex') + return buff.length === 32 ? hex : buff.toString('utf8') + } catch (e) { + return hex + } + } + + renderTypedData = (data) => { + const { domain, message } = JSON.parse(data) + return ( +
    + { + domain + ? ( +
    +

    + Domain +

    + +
    + ) + : '' + } + { + message + ? ( +
    +

    + Message +

    + +
    + ) + : '' + } +
    + ) + } + + renderBody = () => { + let rows + let notice = `${this.context.t('youSign')}:` + + const { txData } = this.props + const { type, msgParams: { data } } = txData + + if (type === 'personal_sign') { + rows = [{ name: this.context.t('message'), value: this.msgHexToText(data) }] + } else if (type === 'eth_signTypedData') { + rows = data + } else if (type === 'eth_sign') { + rows = [{ name: this.context.t('message'), value: data }] + notice = this.context.t('signNotice') + } + + return ( +
    + { this.renderAccountInfo() } + { this.renderRequestInfo() } +
    + { notice } + { + type === 'eth_sign' + ? ( + { + global.platform.openWindow({ + url: 'https://metamask.zendesk.com/hc/en-us/articles/360015488751', + }) + }} + > + { this.context.t('learnMore') } + + ) + : null + } +
    +
    + { + rows.map(({ name, value }, index) => { + if (typeof value === 'boolean') { + value = value.toString() + } + return ( +
    +
    + { `${name}:` } +
    +
    + { value } +
    +
    + ) + }) + } +
    +
    + ) + } + + renderFooter = () => { + const { cancel, sign } = this.props + + return ( +
    + , + +
    + ) + } + + render = () => { + return ( +
    + { this.renderHeader() } + { this.renderBody() } + { this.renderFooter() } +
    + ) + } +} diff --git a/ui/app/components/app/signature-request-original/signature-request-original.container.js b/ui/app/components/app/signature-request-original/signature-request-original.container.js new file mode 100644 index 000000000..be891b4db --- /dev/null +++ b/ui/app/components/app/signature-request-original/signature-request-original.container.js @@ -0,0 +1,72 @@ +import { connect } from 'react-redux' +import { compose } from 'recompose' +import { withRouter } from 'react-router-dom' + +import actions from '../../../store/actions' +import { + getSelectedAccount, + getCurrentAccountWithSendEtherInfo, + getSelectedAddress, + conversionRateSelector, +} from '../../../selectors/selectors.js' +import { clearConfirmTransaction } from '../../../ducks/confirm-transaction/confirm-transaction.duck' +import SignatureRequestOriginal from './signature-request-original.component' + +function mapStateToProps (state) { + return { + balance: getSelectedAccount(state).balance, + selectedAccount: getCurrentAccountWithSendEtherInfo(state), + selectedAddress: getSelectedAddress(state), + requester: null, + requesterAddress: null, + conversionRate: conversionRateSelector(state), + } +} + +function mapDispatchToProps (dispatch) { + return { + goHome: () => dispatch(actions.goHome()), + clearConfirmTransaction: () => dispatch(clearConfirmTransaction()), + } +} + +function mergeProps (stateProps, dispatchProps, ownProps) { + const { + signPersonalMessage, + signTypedMessage, + cancelPersonalMessage, + cancelTypedMessage, + signMessage, + cancelMessage, + txData, + } = ownProps + + const { type } = txData + + let cancel + let sign + if (type === 'personal_sign') { + cancel = cancelPersonalMessage + sign = signPersonalMessage + } else if (type === 'eth_signTypedData') { + cancel = cancelTypedMessage + sign = signTypedMessage + } else if (type === 'eth_sign') { + cancel = cancelMessage + sign = signMessage + } + + return { + ...ownProps, + ...stateProps, + ...dispatchProps, + txData, + cancel, + sign, + } +} + +export default compose( + withRouter, + connect(mapStateToProps, mapDispatchToProps, mergeProps) +)(SignatureRequestOriginal) diff --git a/ui/app/components/app/signature-request/signature-request-header/signature-request-header.component.js b/ui/app/components/app/signature-request/signature-request-header/signature-request-header.component.js index 3ac0c9afb..661a933a0 100644 --- a/ui/app/components/app/signature-request/signature-request-header/signature-request-header.component.js +++ b/ui/app/components/app/signature-request/signature-request-header/signature-request-header.component.js @@ -14,10 +14,12 @@ export default class SignatureRequestHeader extends PureComponent { return (
    - {selectedAccount && } + {selectedAccount && ( + + )} {name}
    diff --git a/ui/app/components/app/signature-request/tests/signature-request.test.js b/ui/app/components/app/signature-request/tests/signature-request.test.js index 68b114dd8..6ff07effc 100644 --- a/ui/app/components/app/signature-request/tests/signature-request.test.js +++ b/ui/app/components/app/signature-request/tests/signature-request.test.js @@ -8,11 +8,16 @@ describe('Signature Request Component', function () { let wrapper beforeEach(() => { - wrapper = shallow() + wrapper = shallow(( + + )) }) describe('render', () => { diff --git a/ui/app/components/app/token-cell.js b/ui/app/components/app/token-cell.js deleted file mode 100644 index 495b9502b..000000000 --- a/ui/app/components/app/token-cell.js +++ /dev/null @@ -1,177 +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 -import Identicon from '../ui/identicon' -const prefixForNetwork = require('../../../lib/etherscan-prefix-for-network') -const selectors = require('../../selectors/selectors') -const actions = require('../../store/actions') -const { conversionUtil, multiplyCurrencies } = require('../../helpers/utils/conversion-util') - -const TokenMenuDropdown = require('./dropdowns/token-menu-dropdown.js') - -function mapStateToProps (state) { - return { - network: state.metamask.network, - currentCurrency: state.metamask.currentCurrency, - selectedTokenAddress: state.metamask.selectedTokenAddress, - userAddress: selectors.getSelectedAddress(state), - contractExchangeRates: state.metamask.contractExchangeRates, - conversionRate: state.metamask.conversionRate, - sidebarOpen: state.appState.sidebar.isOpen, - } -} - -function mapDispatchToProps (dispatch) { - return { - setSelectedToken: address => dispatch(actions.setSelectedToken(address)), - hideSidebar: () => dispatch(actions.hideSidebar()), - } -} - -module.exports = connect(mapStateToProps, mapDispatchToProps)(TokenCell) - -inherits(TokenCell, Component) -function TokenCell () { - Component.call(this) - - this.state = { - tokenMenuOpen: false, - } -} - -TokenCell.contextTypes = { - metricsEvent: PropTypes.func, -} - -TokenCell.prototype.render = function () { - const { tokenMenuOpen } = this.state - const props = this.props - const { - address, - symbol, - string, - network, - setSelectedToken, - selectedTokenAddress, - contractExchangeRates, - conversionRate, - hideSidebar, - sidebarOpen, - currentCurrency, - // userAddress, - image, - } = props - let currentTokenToFiatRate - let currentTokenInFiat - let formattedFiat = '' - - if (contractExchangeRates[address]) { - currentTokenToFiatRate = multiplyCurrencies( - contractExchangeRates[address], - conversionRate - ) - currentTokenInFiat = conversionUtil(string, { - fromNumericBase: 'dec', - fromCurrency: symbol, - toCurrency: currentCurrency.toUpperCase(), - numberOfDecimals: 2, - conversionRate: currentTokenToFiatRate, - }) - formattedFiat = currentTokenInFiat.toString() === '0' - ? '' - : `${currentTokenInFiat} ${currentCurrency.toUpperCase()}` - } - - const showFiat = Boolean(currentTokenInFiat) && currentCurrency.toUpperCase() !== symbol - - return ( - h('div.token-list-item', { - className: `token-list-item ${selectedTokenAddress === address ? 'token-list-item--active' : ''}`, - // style: { cursor: network === '1' ? 'pointer' : 'default' }, - // onClick: this.view.bind(this, address, userAddress, network), - onClick: () => { - setSelectedToken(address) - this.context.metricsEvent({ - eventOpts: { - category: 'Navigation', - action: 'Token Menu', - name: 'Clicked Token', - }, - }) - selectedTokenAddress !== address && sidebarOpen && hideSidebar() - }, - }, [ - - h(Identicon, { - className: 'token-list-item__identicon', - diameter: 50, - address, - network, - image, - }), - - h('div.token-list-item__balance-ellipsis', null, [ - h('div.token-list-item__balance-wrapper', null, [ - h('div.token-list-item__token-balance', `${string || 0}`), - h('div.token-list-item__token-symbol', symbol), - showFiat && h('div.token-list-item__fiat-amount', { - style: {}, - }, formattedFiat), - ]), - - h('i.fa.fa-ellipsis-h.fa-lg.token-list-item__ellipsis.cursor-pointer', { - onClick: (e) => { - e.stopPropagation() - this.setState({ tokenMenuOpen: true }) - }, - }), - - ]), - - - tokenMenuOpen && h(TokenMenuDropdown, { - onClose: () => this.setState({ tokenMenuOpen: false }), - token: { symbol, address }, - }), - - /* - h('button', { - onClick: this.send.bind(this, address), - }, 'SEND'), - */ - - ]) - ) -} - -TokenCell.prototype.send = function (address, event) { - event.preventDefault() - event.stopPropagation() - const url = tokenFactoryFor(address) - if (url) { - navigateTo(url) - } -} - -TokenCell.prototype.view = function (address, userAddress, network) { - const url = etherscanLinkFor(address, userAddress, network) - if (url) { - navigateTo(url) - } -} - -function navigateTo (url) { - global.platform.openWindow({ url }) -} - -function etherscanLinkFor (tokenAddress, address, network) { - const prefix = prefixForNetwork(network) - return `https://${prefix}etherscan.io/token/${tokenAddress}?a=${address}` -} - -function tokenFactoryFor (tokenAddress) { - return `https://tokenfactory.surge.sh/#/token/${tokenAddress}` -} - diff --git a/ui/app/components/app/token-cell/index.js b/ui/app/components/app/token-cell/index.js new file mode 100644 index 000000000..f0f3bcb4a --- /dev/null +++ b/ui/app/components/app/token-cell/index.js @@ -0,0 +1 @@ +export { default } from './token-cell.container' diff --git a/ui/app/components/app/token-cell/token-cell.component.js b/ui/app/components/app/token-cell/token-cell.component.js new file mode 100644 index 000000000..dc2bbb6d3 --- /dev/null +++ b/ui/app/components/app/token-cell/token-cell.component.js @@ -0,0 +1,141 @@ +import classnames from 'classnames' +import PropTypes from 'prop-types' +import React, { Component } from 'react' +import Identicon from '../../ui/identicon' +const prefixForNetwork = require('../../../../lib/etherscan-prefix-for-network') +const { conversionUtil, multiplyCurrencies } = require('../../../helpers/utils/conversion-util') + +const TokenMenuDropdown = require('../dropdowns/token-menu-dropdown.js') + +export default class TokenCell extends Component { + static contextTypes = { + metricsEvent: PropTypes.func, + } + + state = { + tokenMenuOpen: false, + } + + send (address, event) { + event.preventDefault() + event.stopPropagation() + const url = tokenFactoryFor(address) + if (url) { + navigateTo(url) + } + } + + view (address, userAddress, network) { + const url = etherscanLinkFor(address, userAddress, network) + if (url) { + navigateTo(url) + } + } + + render () { + const { tokenMenuOpen } = this.state + const props = this.props + const { + address, + symbol, + string, + network, + setSelectedToken, + selectedTokenAddress, + contractExchangeRates, + conversionRate, + hideSidebar, + sidebarOpen, + currentCurrency, + // userAddress, + image, + } = props + let currentTokenToFiatRate + let currentTokenInFiat + let formattedFiat = '' + + if (contractExchangeRates[address]) { + currentTokenToFiatRate = multiplyCurrencies( + contractExchangeRates[address], + conversionRate + ) + currentTokenInFiat = conversionUtil(string, { + fromNumericBase: 'dec', + fromCurrency: symbol, + toCurrency: currentCurrency.toUpperCase(), + numberOfDecimals: 2, + conversionRate: currentTokenToFiatRate, + }) + formattedFiat = currentTokenInFiat.toString() === '0' + ? '' + : `${currentTokenInFiat} ${currentCurrency.toUpperCase()}` + } + + const showFiat = Boolean(currentTokenInFiat) && currentCurrency.toUpperCase() !== symbol + + return ( +
    { + setSelectedToken(address) + this.context.metricsEvent({ + eventOpts: { + category: 'Navigation', + action: 'Token Menu', + name: 'Clicked Token', + }, + }) + selectedTokenAddress !== address && sidebarOpen && hideSidebar() + }} + > + +
    +
    +
    {string || 0}
    +
    {symbol}
    + {showFiat && ( +
    + {formattedFiat} +
    + )} +
    + { + e.stopPropagation() + this.setState({ tokenMenuOpen: true }) + }} + /> +
    + {tokenMenuOpen && ( + this.setState({ tokenMenuOpen: false })} + token={{ symbol, address }} + /> + )} +
    + ) + } +} + +function navigateTo (url) { + global.platform.openWindow({ url }) +} + +function etherscanLinkFor (tokenAddress, address, network) { + const prefix = prefixForNetwork(network) + return `https://${prefix}etherscan.io/token/${tokenAddress}?a=${address}` +} + +function tokenFactoryFor (tokenAddress) { + return `https://tokenfactory.surge.sh/#/token/${tokenAddress}` +} + diff --git a/ui/app/components/app/token-cell/token-cell.container.js b/ui/app/components/app/token-cell/token-cell.container.js new file mode 100644 index 000000000..176e93008 --- /dev/null +++ b/ui/app/components/app/token-cell/token-cell.container.js @@ -0,0 +1,25 @@ +import { connect } from 'react-redux' +import { setSelectedToken, hideSidebar } from '../../../store/actions' +import { getSelectedAddress } from '../../../selectors/selectors' +import TokenCell from './token-cell.component' + +function mapStateToProps (state) { + return { + network: state.metamask.network, + currentCurrency: state.metamask.currentCurrency, + selectedTokenAddress: state.metamask.selectedTokenAddress, + userAddress: getSelectedAddress(state), + contractExchangeRates: state.metamask.contractExchangeRates, + conversionRate: state.metamask.conversionRate, + sidebarOpen: state.appState.sidebar.isOpen, + } +} + +function mapDispatchToProps (dispatch) { + return { + setSelectedToken: address => dispatch(setSelectedToken(address)), + hideSidebar: () => dispatch(hideSidebar()), + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(TokenCell) diff --git a/ui/app/components/app/token-list.js b/ui/app/components/app/token-list.js index 000ca6b3f..ee2f85cac 100644 --- a/ui/app/components/app/token-list.js +++ b/ui/app/components/app/token-list.js @@ -1,9 +1,8 @@ -const Component = require('react').Component -const PropTypes = require('prop-types') -const h = require('react-hyperscript') +import PropTypes from 'prop-types' +import React, { Component } from 'react' +import TokenCell from './token-cell' const inherits = require('util').inherits const TokenTracker = require('eth-token-tracker') -const TokenCell = require('./token-cell.js') const connect = require('react-redux').connect const selectors = require('../../selectors/selectors') const log = require('loglevel') @@ -44,53 +43,64 @@ function TokenList () { Component.call(this) } -TokenList.prototype.render = function () { +TokenList.prototype.render = function TokenList () { const { userAddress, assetImages } = this.props const state = this.state const { tokens, isLoading, error } = state if (isLoading) { - return this.message(this.context.t('loadingTokens')) + return ( +
    + {this.context.t('loadingTokens')} +
    + ) } if (error) { log.error(error) - return h('.hotFix', { - style: { - padding: '80px', - }, - }, [ - this.context.t('troubleTokenBalances'), - h('span.hotFix', { - style: { - color: 'rgba(247, 134, 28, 1)', - cursor: 'pointer', - }, - onClick: () => { - global.platform.openWindow({ - url: `https://ethplorer.io/address/${userAddress}`, - }) - }, - }, this.context.t('here')), - ]) + return ( +
    + {this.context.t('troubleTokenBalances')} + { + global.platform.openWindow({ + url: `https://ethplorer.io/address/${userAddress}`, + }) + }} + > + {this.context.t('here')} + +
    + ) } - return h('div', tokens.map((tokenData) => { - tokenData.image = assetImages[tokenData.address] - return h(TokenCell, tokenData) - })) - -} - -TokenList.prototype.message = function (body) { - return h('div', { - style: { - display: 'flex', - height: '250px', - alignItems: 'center', - justifyContent: 'center', - padding: '30px', - }, - }, body) + return ( +
    + {tokens.map((tokenData, index) => { + tokenData.image = assetImages[tokenData.address] + return ( + + ) + })} +
    + ) } TokenList.prototype.componentDidMount = function () { @@ -105,7 +115,9 @@ TokenList.prototype.createFreshTokenTracker = function () { this.tracker.removeListener('error', this.showError) } - if (!global.ethereumProvider) return + if (!global.ethereumProvider) { + return + } const { userAddress } = this.props this.tracker = new TokenTracker({ @@ -154,7 +166,9 @@ TokenList.prototype.componentDidUpdate = function (prevProps) { const oldTokensLength = tokens ? tokens.length : 0 const tokensLengthUnchanged = oldTokensLength === newTokens.length - if (tokensLengthUnchanged && shouldUpdateTokens) return + if (tokensLengthUnchanged && shouldUpdateTokens) { + return + } this.setState({ isLoading: true }) this.createFreshTokenTracker() @@ -168,21 +182,10 @@ TokenList.prototype.updateBalances = function (tokens) { } TokenList.prototype.componentWillUnmount = function () { - if (!this.tracker) return + if (!this.tracker) { + return + } this.tracker.stop() this.tracker.removeListener('update', this.balanceUpdater) this.tracker.removeListener('error', this.showError) } - -// function uniqueMergeTokens (tokensA, tokensB = []) { -// const uniqueAddresses = [] -// const result = [] -// tokensA.concat(tokensB).forEach((token) => { -// const normal = normalizeAddress(token.address) -// if (!uniqueAddresses.includes(normal)) { -// uniqueAddresses.push(normal) -// result.push(token) -// } -// }) -// return result -// } diff --git a/ui/app/components/app/transaction-action/tests/transaction-action.component.test.js b/ui/app/components/app/transaction-action/tests/transaction-action.component.test.js index 16f2e256f..f97e81d15 100644 --- a/ui/app/components/app/transaction-action/tests/transaction-action.component.test.js +++ b/ui/app/components/app/transaction-action/tests/transaction-action.component.test.js @@ -35,11 +35,13 @@ describe('TransactionAction Component', () => { }, } - const wrapper = shallow(, { context: { t }}) + const wrapper = shallow(( + + ), { context: { t }}) assert.equal(wrapper.find('.transaction-action').length, 1) wrapper.setState({ transactionAction: 'sentEther' }) diff --git a/ui/app/components/app/transaction-breakdown/transaction-breakdown.component.js b/ui/app/components/app/transaction-breakdown/transaction-breakdown.component.js index 5642e0fa5..0e1073337 100644 --- a/ui/app/components/app/transaction-breakdown/transaction-breakdown.component.js +++ b/ui/app/components/app/transaction-breakdown/transaction-breakdown.component.js @@ -50,10 +50,12 @@ export default class TransactionBreakdown extends PureComponent { className="transaction-breakdown__row-title" > {typeof gas !== 'undefined' - ? + ? ( + + ) : '?' } @@ -72,13 +74,15 @@ export default class TransactionBreakdown extends PureComponent { } {typeof gasPrice !== 'undefined' - ? + ? ( + + ) : '?' } 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 f27c74970..f3bd51b93 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 @@ -39,7 +39,6 @@ export default class TransactionListItemDetails extends PureComponent { state = { justCopied: false, - cancelDisabled: false, } handleEtherscanClick = () => { @@ -192,15 +191,17 @@ export default class TransactionListItemDetails extends PureComponent { { - showRetry && - - + showRetry && ( + + + + ) }
    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 9ab0105f9..3106f7fc3 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 @@ -236,18 +236,22 @@ export default class TransactionListItem extends PureComponent { )} /> { showEstimatedTime - ? + ? ( + + ) : null } { this.renderPrimaryCurrency() } { this.renderSecondaryCurrency() }
    -
    +
    { showTransactionDetails && (
    diff --git a/ui/app/components/app/transaction-view-balance/tests/token-view-balance.component.test.js b/ui/app/components/app/transaction-view-balance/tests/token-view-balance.component.test.js index 0e2882e9c..d948355fc 100644 --- a/ui/app/components/app/transaction-view-balance/tests/token-view-balance.component.test.js +++ b/ui/app/components/app/transaction-view-balance/tests/token-view-balance.component.test.js @@ -25,14 +25,16 @@ describe('TransactionViewBalance Component', () => { }) it('should render ETH balance properly', () => { - const wrapper = shallow(, { context: { t, metricsEvent } }) + const wrapper = shallow(( + + ), { context: { t, metricsEvent } }) assert.equal(wrapper.find('.transaction-view-balance').length, 1) assert.equal(wrapper.find('.transaction-view-balance__button').length, 2) @@ -55,15 +57,17 @@ describe('TransactionViewBalance Component', () => { symbol: 'ABC', } - const wrapper = shallow(, { context: { t } }) + const wrapper = shallow(( + + ), { context: { t } }) assert.equal(wrapper.find('.transaction-view-balance').length, 1) assert.equal(wrapper.find('.transaction-view-balance__button').length, 1) diff --git a/ui/app/components/app/wallet-view.js b/ui/app/components/app/wallet-view.js deleted file mode 100644 index 55aeec333..000000000 --- a/ui/app/components/app/wallet-view.js +++ /dev/null @@ -1,178 +0,0 @@ -const Component = require('react').Component -const PropTypes = require('prop-types') -const connect = require('react-redux').connect -const h = require('react-hyperscript') -const { withRouter } = require('react-router-dom') -const { compose } = require('recompose') -const inherits = require('util').inherits -const { checksumAddress } = require('../../helpers/utils/util') -// const AccountDropdowns = require('./dropdowns/index.js').AccountDropdowns -const actions = require('../../store/actions') -import BalanceComponent from '../ui/balance' -const TokenList = require('./token-list') -const selectors = require('../../selectors/selectors') -const { ADD_TOKEN_ROUTE } = require('../../helpers/constants/routes') - -import AddTokenButton from './add-token-button' -import AccountDetails from './account-details' - -module.exports = compose( - withRouter, - connect(mapStateToProps, mapDispatchToProps) -)(WalletView) - -WalletView.contextTypes = { - t: PropTypes.func, - metricsEvent: PropTypes.func, -} - -WalletView.defaultProps = { - responsiveDisplayClassname: '', -} - -function mapStateToProps (state) { - - return { - network: state.metamask.network, - sidebarOpen: state.appState.sidebar.isOpen, - identities: state.metamask.identities, - accounts: selectors.getMetaMaskAccounts(state), - keyrings: state.metamask.keyrings, - selectedAddress: selectors.getSelectedAddress(state), - selectedAccount: selectors.getSelectedAccount(state), - selectedTokenAddress: state.metamask.selectedTokenAddress, - } -} - -function mapDispatchToProps (dispatch) { - return { - showSendPage: () => dispatch(actions.showSendPage()), - hideSidebar: () => dispatch(actions.hideSidebar()), - unsetSelectedToken: () => dispatch(actions.setSelectedToken()), - showAddTokenPage: () => dispatch(actions.showAddTokenPage()), - } -} - -inherits(WalletView, Component) -function WalletView () { - Component.call(this) -} - -WalletView.prototype.renderWalletBalance = function () { - const { - selectedTokenAddress, - selectedAccount, - unsetSelectedToken, - hideSidebar, - sidebarOpen, - } = this.props - - const selectedClass = selectedTokenAddress - ? '' - : 'wallet-balance-wrapper--active' - const className = `flex-column wallet-balance-wrapper ${selectedClass}` - - return h('div', { className }, [ - h('div.wallet-balance', - { - onClick: () => { - unsetSelectedToken() - selectedTokenAddress && sidebarOpen && hideSidebar() - }, - }, - [ - h(BalanceComponent, { - balanceValue: selectedAccount ? selectedAccount.balance : '', - style: {}, - }), - ] - ), - ]) -} - -WalletView.prototype.renderAddToken = function () { - const { - sidebarOpen, - hideSidebar, - history, - } = this.props - const { metricsEvent } = this.context - - return h(AddTokenButton, { - onClick () { - history.push(ADD_TOKEN_ROUTE) - metricsEvent({ - eventOpts: { - category: 'Navigation', - action: 'Token Menu', - name: 'Clicked "Add Token"', - }, - }) - if (sidebarOpen) { - hideSidebar() - } - }, - }) -} - -WalletView.prototype.render = function () { - const { - responsiveDisplayClassname, - selectedAddress, - keyrings, - identities, - network, - } = this.props - // temporary logs + fake extra wallets - - const checksummedAddress = checksumAddress(selectedAddress, network) - - if (!selectedAddress) { - throw new Error('selectedAddress should not be ' + String(selectedAddress)) - } - - const keyring = keyrings.find((kr) => { - return kr.accounts.includes(selectedAddress) - }) - - let label = '' - let type - if (keyring) { - type = keyring.type - if (type !== 'HD Key Tree') { - if (type.toLowerCase().search('hardware') !== -1) { - label = this.context.t('hardware') - } else { - label = this.context.t('imported') - } - } - } - - return h('div.wallet-view.flex-column', { - style: {}, - className: responsiveDisplayClassname, - }, [ - - h(AccountDetails, { - label, - checksummedAddress, - name: identities[selectedAddress].name, - }), - - this.renderWalletBalance(), - - h(TokenList), - - this.renderAddToken(), - ]) -} - -// TODO: Extra wallets, for dev testing. Remove when PRing to master. -// const extraWallet = h('div.flex-column.wallet-balance-wrapper', {}, [ -// h('div.wallet-balance', {}, [ -// h(BalanceComponent, { -// balanceValue: selectedAccount.balance, -// style: {}, -// }), -// ]), -// ]) diff --git a/ui/app/components/app/wallet-view/index.js b/ui/app/components/app/wallet-view/index.js new file mode 100644 index 000000000..bc8fd26ab --- /dev/null +++ b/ui/app/components/app/wallet-view/index.js @@ -0,0 +1 @@ +export { default } from './wallet-view.container' diff --git a/ui/app/components/app/wallet-view/wallet-view.component.js b/ui/app/components/app/wallet-view/wallet-view.component.js new file mode 100644 index 000000000..dc3abf259 --- /dev/null +++ b/ui/app/components/app/wallet-view/wallet-view.component.js @@ -0,0 +1,147 @@ +import classnames from 'classnames' +import PropTypes from 'prop-types' +import React, { Component } from 'react' +import BalanceComponent from '../../ui/balance' +import AddTokenButton from '../add-token-button' +import AccountDetails from '../account-details' + +const { checksumAddress } = require('../../../helpers/utils/util') +const TokenList = require('../token-list') +const { ADD_TOKEN_ROUTE, CONNECTED_ROUTE } = require('../../../helpers/constants/routes') + +export default class WalletView extends Component { + static contextTypes = { + t: PropTypes.func, + metricsEvent: PropTypes.func, + } + + static defaultProps = { + responsiveDisplayClassname: '', + selectedAccount: null, + selectedTokenAddress: null, + } + + static propTypes = { + selectedTokenAddress: PropTypes.string, + selectedAccount: PropTypes.object, + selectedAddress: PropTypes.string.isRequired, + keyrings: PropTypes.array.isRequired, + responsiveDisplayClassname: PropTypes.string, + identities: PropTypes.object.isRequired, + history: PropTypes.object.isRequired, + unsetSelectedToken: PropTypes.func.isRequired, + sidebarOpen: PropTypes.bool.isRequired, + hideSidebar: PropTypes.func.isRequired, + } + + renderWalletBalance () { + const { + selectedTokenAddress, + selectedAccount, + unsetSelectedToken, + hideSidebar, + sidebarOpen, + } = this.props + + return ( +
    +
    { + unsetSelectedToken() + selectedTokenAddress && sidebarOpen && hideSidebar() + }} + > + +
    +
    + ) + } + + renderAddToken () { + const { + sidebarOpen, + hideSidebar, + history, + } = this.props + const { metricsEvent } = this.context + + return ( + { + history.push(ADD_TOKEN_ROUTE) + metricsEvent({ + eventOpts: { + category: 'Navigation', + action: 'Token Menu', + name: 'Clicked "Add Token"', + }, + }) + if (sidebarOpen) { + hideSidebar() + } + }} + /> + ) + } + + showConnectedSites = () => { + const { + sidebarOpen, + hideSidebar, + history, + } = this.props + history.push(CONNECTED_ROUTE) + if (sidebarOpen) { + hideSidebar() + } + } + + render () { + const { + responsiveDisplayClassname, + selectedAddress, + keyrings, + identities, + } = this.props + + const checksummedAddress = checksumAddress(selectedAddress) + + const keyring = keyrings.find((kr) => { + return kr.accounts.includes(selectedAddress) + }) + + let label = '' + let type + if (keyring) { + type = keyring.type + if (type !== 'HD Key Tree') { + if (type.toLowerCase().search('hardware') !== -1) { + label = this.context.t('hardware') + } else { + label = this.context.t('imported') + } + } + } + + return ( +
    + + {this.renderWalletBalance()} + + {this.renderAddToken()} +
    + ) + } +} diff --git a/ui/app/components/app/wallet-view/wallet-view.container.js b/ui/app/components/app/wallet-view/wallet-view.container.js new file mode 100644 index 000000000..f0329e3c3 --- /dev/null +++ b/ui/app/components/app/wallet-view/wallet-view.container.js @@ -0,0 +1,33 @@ +import { connect } from 'react-redux' +import { withRouter } from 'react-router-dom' +import { compose } from 'recompose' +import WalletView from './wallet-view.component' +import {showSendPage, hideSidebar, setSelectedToken, showAddTokenPage} from '../../../store/actions' +import * as selectors from '../../../selectors/selectors' + +function mapStateToProps (state) { + return { + network: state.metamask.network, + sidebarOpen: state.appState.sidebar.isOpen, + identities: state.metamask.identities, + accounts: selectors.getMetaMaskAccounts(state), + keyrings: state.metamask.keyrings, + selectedAddress: selectors.getSelectedAddress(state), + selectedAccount: selectors.getSelectedAccount(state), + selectedTokenAddress: state.metamask.selectedTokenAddress, + } +} + +function mapDispatchToProps (dispatch) { + return { + showSendPage: () => dispatch(showSendPage()), + hideSidebar: () => dispatch(hideSidebar()), + unsetSelectedToken: () => dispatch(setSelectedToken()), + showAddTokenPage: () => dispatch(showAddTokenPage()), + } +} + +export default compose( + withRouter, + connect(mapStateToProps, mapDispatchToProps) +)(WalletView) diff --git a/ui/app/components/ui/alert/index.js b/ui/app/components/ui/alert/index.js index da2ca4b66..7d09de3cb 100644 --- a/ui/app/components/ui/alert/index.js +++ b/ui/app/components/ui/alert/index.js @@ -1,17 +1,12 @@ -const { Component } = require('react') -const PropTypes = require('prop-types') -const h = require('react-hyperscript') +import classnames from 'classnames' +import PropTypes from 'prop-types' +import React, { Component } from 'react' class Alert extends Component { - - constructor (props) { - super(props) - - this.state = { - visble: false, - msg: false, - className: '', - } + state = { + visible: false, + msg: false, + className: '', } componentWillReceiveProps (nextProps) { @@ -26,14 +21,14 @@ class Alert extends Component { this.setState({ msg: props.msg, visible: true, - className: '.visible', + className: 'visible', }) } animateOut () { this.setState({ msg: null, - className: '.hidden', + className: 'hidden', }) setTimeout(_ => { @@ -45,9 +40,9 @@ class Alert extends Component { render () { if (this.state.visible) { return ( - h(`div.global-alert${this.state.className}`, {}, - h('a.msg', {}, this.state.msg) - ) + ) } return null diff --git a/ui/app/components/ui/button-group/button-group.stories.js b/ui/app/components/ui/button-group/button-group.stories.js index c58c628b3..1596680c1 100644 --- a/ui/app/components/ui/button-group/button-group.stories.js +++ b/ui/app/components/ui/button-group/button-group.stories.js @@ -6,7 +6,7 @@ import Button from '../button' import { text, boolean } from '@storybook/addon-knobs/react' storiesOf('ButtonGroup', module) - .add('with Buttons', () => + .add('with Buttons', () => ( - ) - .add('with a disabled Button', () => + )) + .add('with a disabled Button', () => ( - ) + )) diff --git a/ui/app/components/ui/button-group/tests/button-group-component.test.js b/ui/app/components/ui/button-group/tests/button-group-component.test.js index 663d86c74..b07b16cc9 100644 --- a/ui/app/components/ui/button-group/tests/button-group-component.test.js +++ b/ui/app/components/ui/button-group/tests/button-group-component.test.js @@ -21,12 +21,16 @@ describe('ButtonGroup Component', function () { let wrapper beforeEach(() => { - wrapper = shallow({mockButtons}) + wrapper = shallow(( + + {mockButtons} + + )) }) afterEach(() => { diff --git a/ui/app/components/ui/button/button.stories.js b/ui/app/components/ui/button/button.stories.js index 9df53439d..6540d5aa1 100644 --- a/ui/app/components/ui/button/button.stories.js +++ b/ui/app/components/ui/button/button.stories.js @@ -6,7 +6,7 @@ import { text, boolean } from '@storybook/addon-knobs/react' // ', 'secondary', 'default', 'warning', 'danger', 'danger-primary', 'link'], 'primary')} storiesOf('Button', module) - .add('Button - Primary', () => + .add('Button - Primary', () => ( - ) - .add('Button - Secondary', () => + )) + .add('Button - Secondary', () => ( - ) - .add('Button - Default', () => + )) + .add('Button - Default', () => ( - ) - .add('Button - Warning', () => + )) + .add('Button - Warning', () => ( - ) - .add('Button - Danger', () => + )) + .add('Button - Danger', () => ( - ) - .add('Button - Danger Primary', () => + )) + .add('Button - Danger Primary', () => ( - ) - .add('Button - Link', () => + )) + .add('Button - Link', () => ( - ) + )) diff --git a/ui/app/components/ui/copyButton.js b/ui/app/components/ui/copyButton.js index a60d33523..d235fea04 100644 --- a/ui/app/components/ui/copyButton.js +++ b/ui/app/components/ui/copyButton.js @@ -1,66 +1,66 @@ -const Component = require('react').Component -const PropTypes = require('prop-types') -const h = require('react-hyperscript') -const inherits = require('util').inherits -const copyToClipboard = require('copy-to-clipboard') -const connect = require('react-redux').connect +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import { connect } from 'react-redux' +const copyToClipboard = require('copy-to-clipboard') const Tooltip = require('./tooltip') -CopyButton.contextTypes = { - t: PropTypes.func, +class CopyButton extends Component { + static contextTypes = { + t: PropTypes.func, + } + + static defaultProps = { + title: null, + } + + static propTypes = { + value: PropTypes.string.isRequired, + title: PropTypes.string, + } + + state = {} + + debounceRestore = () => { + this.setState({ copied: true }) + clearTimeout(this.timeout) + this.timeout = setTimeout(() => { + this.setState({ copied: false }) + }, 850) + } + + render () { + const state = this.state + const props = this.props + const value = props.value + const copied = state.copied + const message = copied ? this.context.t('copiedButton') : props.title || this.context.t('copyButton') + + return ( +
    + + { + event.preventDefault() + event.stopPropagation() + copyToClipboard(value) + this.debounceRestore() + }} + /> + +
    + ) + } } module.exports = connect()(CopyButton) - - -inherits(CopyButton, Component) -function CopyButton () { - Component.call(this) -} - -// As parameters, accepts: -// "value", which is the value to copy (mandatory) -// "title", which is the text to show on hover (optional, defaults to 'Copy') -CopyButton.prototype.render = function () { - const props = this.props - const state = this.state || {} - - const value = props.value - const copied = state.copied - - const message = copied ? this.context.t('copiedButton') : props.title || this.context.t('copyButton') - - return h('.copy-button', { - style: { - display: 'flex', - alignItems: 'center', - }, - }, [ - - h(Tooltip, { - title: message, - }, [ - h('i.fa.fa-clipboard.cursor-pointer.color-orange', { - style: { - margin: '5px', - }, - onClick: (event) => { - event.preventDefault() - event.stopPropagation() - copyToClipboard(value) - this.debounceRestore() - }, - }), - ]), - - ]) -} - -CopyButton.prototype.debounceRestore = function () { - this.setState({ copied: true }) - clearTimeout(this.timeout) - this.timeout = setTimeout(() => { - this.setState({ copied: false }) - }, 850) -} diff --git a/ui/app/components/ui/currency-display/tests/currency-display.component.test.js b/ui/app/components/ui/currency-display/tests/currency-display.component.test.js index d9ef052f1..387493da6 100644 --- a/ui/app/components/ui/currency-display/tests/currency-display.component.test.js +++ b/ui/app/components/ui/currency-display/tests/currency-display.component.test.js @@ -5,21 +5,25 @@ import CurrencyDisplay from '../currency-display.component' describe('CurrencyDisplay Component', () => { it('should render text with a className', () => { - const wrapper = shallow() + const wrapper = shallow(( + + )) assert.ok(wrapper.hasClass('currency-display')) assert.equal(wrapper.text(), '$123.45') }) it('should render text with a prefix', () => { - const wrapper = shallow() + const wrapper = shallow(( + + )) assert.ok(wrapper.hasClass('currency-display')) assert.equal(wrapper.text(), '-$123.45') diff --git a/ui/app/components/ui/editable-label.js b/ui/app/components/ui/editable-label.js index 8eb10e174..43b288819 100644 --- a/ui/app/components/ui/editable-label.js +++ b/ui/app/components/ui/editable-label.js @@ -1,16 +1,17 @@ -const { Component } = require('react') -const PropTypes = require('prop-types') -const h = require('react-hyperscript') -const classnames = require('classnames') +import classnames from 'classnames' +import PropTypes from 'prop-types' +import React, { Component } from 'react' class EditableLabel extends Component { - constructor (props) { - super(props) + static propTypes = { + onSubmit: PropTypes.func.isRequired, + defaultValue: PropTypes.string, + className: PropTypes.string, + } - this.state = { - isEditing: false, - value: props.defaultValue || '', - } + state = { + isEditing: false, + value: this.props.defaultValue || '', } handleSubmit () { @@ -24,46 +25,41 @@ class EditableLabel extends Component { .then(() => this.setState({ isEditing: false })) } - saveIfEnter (event) { - if (event.key === 'Enter') { - this.handleSubmit() - } - } - renderEditing () { const { value } = this.state - return ([ - h('input.large-input.editable-label__input', { - type: 'text', - required: true, - dir: 'auto', - value: this.state.value, - onKeyPress: (event) => { + return [( + { if (event.key === 'Enter') { this.handleSubmit() } - }, - onChange: event => this.setState({ value: event.target.value }), - className: classnames({ 'editable-label__input--error': value === '' }), - }), - h('div.editable-label__icon-wrapper', [ - h('i.fa.fa-check.editable-label__icon', { - onClick: () => this.handleSubmit(), - }), - ]), - ]) + }} + onChange={event => this.setState({ value: event.target.value })} + className={classnames('large-input', 'editable-label__input', { + 'editable-label__input--error': value === '', + })} + /> + ), ( +
    + this.handleSubmit()} /> +
    + )] } renderReadonly () { - return ([ - h('div.editable-label__value', this.state.value), - h('div.editable-label__icon-wrapper', [ - h('i.fa.fa-pencil.editable-label__icon', { - onClick: () => this.setState({ isEditing: true }), - }), - ]), - ]) + return [( +
    {this.state.value}
    + ), ( +
    + this.setState({ isEditing: true })} /> +
    + )] } render () { @@ -71,19 +67,15 @@ class EditableLabel extends Component { const { className } = this.props return ( - h('div.editable-label', { className: classnames(className) }, - isEditing - ? this.renderEditing() - : this.renderReadonly() - ) +
    + { + isEditing + ? this.renderEditing() + : this.renderReadonly() + } +
    ) } } -EditableLabel.propTypes = { - onSubmit: PropTypes.func.isRequired, - defaultValue: PropTypes.string, - className: PropTypes.string, -} - module.exports = EditableLabel diff --git a/ui/app/components/ui/eth-balance.js b/ui/app/components/ui/eth-balance.js deleted file mode 100644 index 7d577b716..000000000 --- a/ui/app/components/ui/eth-balance.js +++ /dev/null @@ -1,102 +0,0 @@ -const { Component } = require('react') -const h = require('react-hyperscript') -const connect = require('react-redux').connect -const { inherits } = require('util') -const { - formatBalance, - generateBalanceObject, -} = require('../../helpers/utils/util') -const Tooltip = require('./tooltip.js') -const FiatValue = require('./fiat-value.js') - -module.exports = connect(mapStateToProps)(EthBalanceComponent) -function mapStateToProps (state) { - return { - ticker: state.metamask.ticker, - } -} - -inherits(EthBalanceComponent, Component) -function EthBalanceComponent () { - Component.call(this) -} - -EthBalanceComponent.prototype.render = function () { - const props = this.props - const { ticker, value, style, width, needsParse = true } = props - - const formattedValue = value ? formatBalance(value, 6, needsParse, ticker) : '...' - - return ( - - h('.ether-balance.ether-balance-amount', { - style, - }, [ - h('div', { - style: { - display: 'inline', - width, - }, - }, this.renderBalance(formattedValue)), - ]) - - ) -} -EthBalanceComponent.prototype.renderBalance = function (value) { - if (value === 'None') return value - if (value === '...') return value - - const { - conversionRate, - shorten, - incoming, - currentCurrency, - hideTooltip, - styleOveride = {}, - showFiat = true, - } = this.props - const { fontSize, color, fontFamily, lineHeight } = styleOveride - - const { shortBalance, balance, label } = generateBalanceObject(value, shorten ? 1 : 3) - const balanceToRender = shorten ? shortBalance : balance - - const [ethNumber, ethSuffix] = value.split(' ') - const containerProps = hideTooltip ? {} : { - position: 'bottom', - title: `${ethNumber} ${ethSuffix}`, - } - - return ( - h(hideTooltip ? 'div' : Tooltip, - containerProps, - h('div.flex-column', [ - h('.flex-row', { - style: { - alignItems: 'flex-end', - lineHeight: lineHeight || '13px', - fontFamily: fontFamily || 'Montserrat Light', - textRendering: 'geometricPrecision', - }, - }, [ - h('div', { - style: { - width: '100%', - textAlign: 'right', - fontSize: fontSize || 'inherit', - color: color || 'inherit', - }, - }, incoming ? `+${balanceToRender}` : balanceToRender), - h('div', { - style: { - color: color || '#AEAEAE', - fontSize: fontSize || '12px', - marginLeft: '5px', - }, - }, label), - ]), - - showFiat ? h(FiatValue, { value: this.props.value, conversionRate, currentCurrency }) : null, - ]) - ) - ) -} diff --git a/ui/app/components/ui/eth-balance/eth-balance.component.js b/ui/app/components/ui/eth-balance/eth-balance.component.js new file mode 100644 index 000000000..925395602 --- /dev/null +++ b/ui/app/components/ui/eth-balance/eth-balance.component.js @@ -0,0 +1,137 @@ +import PropTypes from 'prop-types' +import React, {Component} from 'react' + +const { + formatBalance, + generateBalanceObject, +} = require('../../../helpers/utils/util') +const Tooltip = require('../tooltip.js') +const FiatValue = require('../fiat-value.js') + +export default class EthBalance extends Component { + static defaultProps = { + style: null, + styleOverride: {}, + showFiat: true, + needsParse: true, + width: undefined, + shorten: false, + incoming: false, + } + + static propTypes = { + conversionRate: PropTypes.any.isRequired, + shorten: PropTypes.bool, + incoming: PropTypes.bool, + currentCurrency: PropTypes.string.isRequired, + hideTooltip: PropTypes.bool, + styleOverride: PropTypes.object, + showFiat: PropTypes.bool, + ticker: PropTypes.string.isRequired, + value: PropTypes.string.isRequired, + style: PropTypes.object, + width: PropTypes.string, + needsParse: PropTypes.bool, + } + + renderBalance (value) { + if (value === 'None') { + return value + } + if (value === '...') { + return value + } + + const { + conversionRate, + shorten, + incoming, + currentCurrency, + hideTooltip, + styleOverride = {}, + showFiat = true, + } = this.props + const { fontSize, color, fontFamily, lineHeight } = styleOverride + + const { shortBalance, balance, label } = generateBalanceObject(value, shorten ? 1 : 3) + const balanceToRender = shorten ? shortBalance : balance + + const [ethNumber, ethSuffix] = value.split(' ') + const containerProps = hideTooltip ? {} : { + position: 'bottom', + title: `${ethNumber} ${ethSuffix}`, + } + + const TooltipComponent = hideTooltip ? 'div' : Tooltip + + return ( + +
    +
    +
    + { + incoming + ? `+${balanceToRender}` + : balanceToRender + } +
    +
    + {label} +
    +
    +
    + { + showFiat + ? ( + + ) + : null + } +
    + ) + } + + render () { + const { ticker, value, style, width, needsParse } = this.props + const formattedValue = value + ? formatBalance(value, 6, needsParse, ticker) + : '...' + + return ( +
    +
    + {this.renderBalance(formattedValue)} +
    +
    + ) + } +} diff --git a/ui/app/components/ui/eth-balance/eth-balance.container.js b/ui/app/components/ui/eth-balance/eth-balance.container.js new file mode 100644 index 000000000..8e36b6f78 --- /dev/null +++ b/ui/app/components/ui/eth-balance/eth-balance.container.js @@ -0,0 +1,10 @@ +import { connect } from 'react-redux' +import EthBalance from './eth-balance.component' + +function mapStateToProps (state) { + return { + ticker: state.metamask.ticker, + } +} + +export default connect(mapStateToProps)(EthBalance) diff --git a/ui/app/components/ui/eth-balance/index.js b/ui/app/components/ui/eth-balance/index.js new file mode 100644 index 000000000..f40991784 --- /dev/null +++ b/ui/app/components/ui/eth-balance/index.js @@ -0,0 +1 @@ +export { default } from './eth-balance.container' diff --git a/ui/app/components/ui/export-text-container/export-text-container.component.js b/ui/app/components/ui/export-text-container/export-text-container.component.js index 21fd5ecec..23ae0b047 100644 --- a/ui/app/components/ui/export-text-container/export-text-container.component.js +++ b/ui/app/components/ui/export-text-container/export-text-container.component.js @@ -1,6 +1,5 @@ -const { Component } = require('react') +import React, { Component } from 'react' const PropTypes = require('prop-types') -const h = require('react-hyperscript') const copyToClipboard = require('copy-to-clipboard') const { exportAsFile } = require('../../../helpers/utils/util') @@ -10,25 +9,33 @@ class ExportTextContainer extends Component { const { t } = this.context return ( - h('.export-text-container', [ - h('.export-text-container__text-container', [ - h('.export-text-container__text.notranslate', text), - ]), - h('.export-text-container__buttons-container', [ - h('.export-text-container__button.export-text-container__button--copy', { - onClick: () => copyToClipboard(text), - }, [ - h('img', { src: 'images/copy-to-clipboard.svg' }), - h('.export-text-container__button-text', t('copyToClipboard')), - ]), - h('.export-text-container__button', { - onClick: () => exportAsFile(filename, text), - }, [ - h('img', { src: 'images/download.svg' }), - h('.export-text-container__button-text', t('saveAsCsvFile')), - ]), - ]), - ]) +
    +
    +
    + {text} +
    +
    +
    +
    copyToClipboard(text)} + > + +
    + {t('copyToClipboard')} +
    +
    +
    exportAsFile(filename, text)} + > + +
    + {t('saveAsCsvFile')} +
    +
    +
    +
    ) } } diff --git a/ui/app/components/ui/fiat-value.js b/ui/app/components/ui/fiat-value.js index 02111ba49..a02264b01 100644 --- a/ui/app/components/ui/fiat-value.js +++ b/ui/app/components/ui/fiat-value.js @@ -1,5 +1,4 @@ -const Component = require('react').Component -const h = require('react-hyperscript') +import React, { Component } from 'react' const inherits = require('util').inherits const formatBalance = require('../../helpers/utils/util').formatBalance @@ -17,9 +16,11 @@ FiatValue.prototype.render = function () { const value = formatBalance(props.value, 6) - if (value === 'None') return value - var fiatDisplayNumber, fiatTooltipNumber - var splitBalance = value.split(' ') + if (value === 'None') { + return value + } + let fiatDisplayNumber, fiatTooltipNumber + const splitBalance = value.split(' ') if (conversionRate !== 0) { fiatTooltipNumber = Number(splitBalance[0]) * conversionRate @@ -36,31 +37,38 @@ function fiatDisplay (fiatDisplayNumber, fiatSuffix, styleOveride = {}) { const { fontSize, color, fontFamily, lineHeight } = styleOveride if (fiatDisplayNumber !== 'N/A') { - return h('.flex-row', { - style: { - alignItems: 'flex-end', - lineHeight: lineHeight || '13px', - fontFamily: fontFamily || 'Montserrat Light', - textRendering: 'geometricPrecision', - }, - }, [ - h('div', { - style: { - width: '100%', - textAlign: 'right', - fontSize: fontSize || '12px', - color: color || '#333333', - }, - }, fiatDisplayNumber), - h('div', { - style: { - color: color || '#AEAEAE', - marginLeft: '5px', - fontSize: fontSize || '12px', - }, - }, fiatSuffix), - ]) + return ( +
    +
    + {fiatDisplayNumber} +
    +
    + {fiatSuffix} +
    +
    + ) } else { - return h('div') + return
    } } diff --git a/ui/app/components/ui/hex-to-decimal/tests/hex-to-decimal.component.test.js b/ui/app/components/ui/hex-to-decimal/tests/hex-to-decimal.component.test.js index c98da9ad4..848ef2c4f 100644 --- a/ui/app/components/ui/hex-to-decimal/tests/hex-to-decimal.component.test.js +++ b/ui/app/components/ui/hex-to-decimal/tests/hex-to-decimal.component.test.js @@ -5,20 +5,24 @@ import HexToDecimal from '../hex-to-decimal.component' describe('HexToDecimal Component', () => { it('should render a prefixed hex as a decimal with a className', () => { - const wrapper = shallow() + const wrapper = shallow(( + + )) assert.ok(wrapper.hasClass('hex-to-decimal')) assert.equal(wrapper.text(), '12345') }) it('should render an unprefixed hex as a decimal with a className', () => { - const wrapper = shallow() + const wrapper = shallow(( + + )) assert.ok(wrapper.hasClass('hex-to-decimal')) assert.equal(wrapper.text(), '6789') diff --git a/ui/app/components/ui/icon-with-fallback/icon-with-fallback.component.js b/ui/app/components/ui/icon-with-fallback/icon-with-fallback.component.js new file mode 100644 index 000000000..13b3e93d7 --- /dev/null +++ b/ui/app/components/ui/icon-with-fallback/icon-with-fallback.component.js @@ -0,0 +1,42 @@ +import React, { PureComponent } from 'react' +import PropTypes from 'prop-types' + +export default class IconWithFallback extends PureComponent { + static propTypes = { + icon: PropTypes.string, + name: PropTypes.string, + } + + static defaultProps = { + name: '', + icon: null, + } + + state = { + iconError: false, + } + + render () { + const { icon, name } = this.props + + return ( +
    +
    + { !this.state.iconError && icon + ? ( + this.setState({ iconError: true })} + /> + ) + : ( + + { name.length ? name.charAt(0).toUpperCase() : '' } + + ) + } +
    + ) + } +} diff --git a/ui/app/components/ui/icon-with-fallback/index.js b/ui/app/components/ui/icon-with-fallback/index.js new file mode 100644 index 000000000..8c1f9a154 --- /dev/null +++ b/ui/app/components/ui/icon-with-fallback/index.js @@ -0,0 +1 @@ +export { default } from './icon-with-fallback.component' diff --git a/ui/app/components/ui/icon-with-fallback/index.scss b/ui/app/components/ui/icon-with-fallback/index.scss new file mode 100644 index 000000000..02ffe371d --- /dev/null +++ b/ui/app/components/ui/icon-with-fallback/index.scss @@ -0,0 +1,30 @@ +.icon-with-fallback { + &__identicon-container { + position: relative; + display: flex; + justify-content: center; + align-items: center; + height: 32px; + width: 32px; + } + + &__identicon-border { + height: 32px; + width: 32px; + border-radius: 50%; + border: 1px solid #F2F3F4; + position: absolute; + background: #FFFFFF; + } + + &__identicon { + width: 24px; + height: 24px; + z-index: 1; + + &--default { + z-index: 1; + color: black; + } + } +} diff --git a/ui/app/components/ui/identicon/tests/identicon.component.test.js b/ui/app/components/ui/identicon/tests/identicon.component.test.js index 2944818f5..b1efb158c 100644 --- a/ui/app/components/ui/identicon/tests/identicon.component.test.js +++ b/ui/app/components/ui/identicon/tests/identicon.component.test.js @@ -18,7 +18,7 @@ describe('Identicon', () => { it('renders default eth_logo identicon with no props', () => { const wrapper = mount( - + ) assert.equal(wrapper.find('img.balance-icon').prop('src'), './images/eth_logo.svg') diff --git a/ui/app/components/ui/loading-screen/loading-screen.component.js b/ui/app/components/ui/loading-screen/loading-screen.component.js index 6b843cfee..b3fb3308d 100644 --- a/ui/app/components/ui/loading-screen/loading-screen.component.js +++ b/ui/app/components/ui/loading-screen/loading-screen.component.js @@ -1,31 +1,33 @@ -const { Component } = require('react') -const h = require('react-hyperscript') +import React, {Component} from 'react' const PropTypes = require('prop-types') const Spinner = require('../spinner') class LoadingScreen extends Component { + static defaultProps = { + loadingMessage: null, + } + + static propTypes = { + loadingMessage: PropTypes.string, + } + renderMessage () { const { loadingMessage } = this.props - return loadingMessage && h('span', loadingMessage) + return loadingMessage + ? {loadingMessage} + : null } render () { return ( - h('.loading-overlay', [ - h('.loading-overlay__container', [ - h(Spinner, { - color: '#F7C06C', - }), - - this.renderMessage(), - ]), - ]) +
    +
    + + {this.renderMessage()} +
    +
    ) } } -LoadingScreen.propTypes = { - loadingMessage: PropTypes.string, -} - module.exports = LoadingScreen diff --git a/ui/app/components/ui/mascot.js b/ui/app/components/ui/mascot.js index 3b0d3e31b..f84961745 100644 --- a/ui/app/components/ui/mascot.js +++ b/ui/app/components/ui/mascot.js @@ -1,6 +1,5 @@ +import React, { Component } from 'react' const inherits = require('util').inherits -const Component = require('react').Component -const h = require('react-hyperscript') const metamaskLogo = require('metamask-logo') const debounce = require('debounce') @@ -20,20 +19,22 @@ function Mascot ({width = '200', height = '200'}) { this.unfollowMouse = this.logo.setFollowMouse.bind(this.logo, false) } -Mascot.prototype.render = function () { +Mascot.prototype.render = function Mascot () { // this is a bit hacky // the event emitter is on `this.props` // and we dont get that until render this.handleAnimationEvents() - - return h('#metamask-mascot-container', { - style: { zIndex: 0 }, - }) + return ( +
    + ) } Mascot.prototype.componentDidMount = function () { - var targetDivId = 'metamask-mascot-container' - var container = document.getElementById(targetDivId) + const targetDivId = 'metamask-mascot-container' + const container = document.getElementById(targetDivId) container.appendChild(this.logo.container) } @@ -46,7 +47,9 @@ Mascot.prototype.componentWillUnmount = function () { Mascot.prototype.handleAnimationEvents = function () { // only setup listeners once - if (this.animations) return + if (this.animations) { + return + } this.animations = this.props.animationEventEmitter this.animations.on('point', this.lookAt.bind(this)) this.animations.on('setFollowMouse', this.logo.setFollowMouse.bind(this.logo)) 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 a2cf0100b..338df83d8 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 @@ -14,6 +14,7 @@ export default class PageContainerFooter extends Component { disabled: PropTypes.bool, submitButtonType: PropTypes.string, hideCancel: PropTypes.bool, + buttonSizeLarge: PropTypes.bool, } static contextTypes = { @@ -31,24 +32,27 @@ export default class PageContainerFooter extends Component { submitButtonType, hideCancel, cancelButtonType, + buttonSizeLarge = false, } = this.props return (
    - {!hideCancel && } + {!hideCancel && ( + + )} + +
    + ) } renderButtons () { @@ -139,40 +133,49 @@ class AccountList extends Component { buttonProps.disabled = true } - return h('div.new-account-connect-form__buttons', {}, [ - h(Button, { - type: 'default', - large: true, - className: 'new-account-connect-form__button', - onClick: this.props.onCancel.bind(this), - }, [this.context.t('cancel')]), - - h(Button, { - type: 'primary', - large: true, - className: 'new-account-connect-form__button unlock', - disabled, - onClick: this.props.onUnlockAccount.bind(this, this.props.device), - }, [this.context.t('unlock')]), - ]) + return ( +
    + + +
    + ) } renderForgetDevice () { - return h('div.hw-forget-device-container', {}, [ - h('a', { - onClick: this.props.onForgetDevice.bind(this, this.props.device), - }, this.context.t('forgetDevice')), - ]) + return ( +
    + ) } render () { - return h('div.new-account-connect-form.account-list', {}, [ - this.renderHeader(), - this.renderAccounts(), - this.renderPagination(), - this.renderButtons(), - this.renderForgetDevice(), - ]) + return ( +
    + {this.renderHeader()} + {this.renderAccounts()} + {this.renderPagination()} + {this.renderButtons()} + {this.renderForgetDevice()} +
    + ) } } 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 3b45e7293..6be85f5b7 100644 --- a/ui/app/pages/create-account/connect-hardware/connect-screen.js +++ b/ui/app/pages/create-account/connect-hardware/connect-screen.js @@ -1,202 +1,228 @@ -const { Component } = require('react') -const PropTypes = require('prop-types') -const h = require('react-hyperscript') +import classnames from 'classnames' +import PropTypes from 'prop-types' +import React, { Component } from 'react' import Button from '../../../components/ui/button' class ConnectScreen extends Component { - constructor (props) { - super(props) - this.state = { - selectedDevice: null, + static contextTypes = { + t: PropTypes.func, + } + + static propTypes = { + connectToHardwareWallet: PropTypes.func.isRequired, + browserSupported: PropTypes.bool.isRequired, + } + + state = { + selectedDevice: null, + } + + connect = () => { + if (this.state.selectedDevice) { + this.props.connectToHardwareWallet(this.state.selectedDevice) + } + return null + } + + renderConnectToTrezorButton () { + return ( + + ) + } + + renderConnectToLedgerButton () { + return ( + + ) + } + + renderButtons () { + return ( +
    +
    + {this.renderConnectToLedgerButton()} + {this.renderConnectToTrezorButton()} +
    + +
    + ) + } + + renderUnsupportedBrowser () { + return ( +
    +
    +

    {this.context.t('browserNotSupported')}

    +

    {this.context.t('chromeRequiredForHardwareWallets')}

    +
    + +
    + ) + } + + renderHeader () { + return ( +
    +

    {this.context.t('hardwareWallets')}

    +

    {this.context.t('hardwareWalletsMsg')}

    +
    + ) + } + + getAffiliateLinks () { + const links = { + trezor: `Trezor`, + ledger: `Ledger`, + } + + const text = this.context.t('orderOneHere') + const response = text.replace('Trezor', links.trezor).replace('Ledger', links.ledger) + + return ( +
    + ) + } + + renderTrezorAffiliateLink () { + return ( +
    +

    {this.context.t('dontHaveAHardwareWallet')}

    + {this.getAffiliateLinks()} +
    + ) + } + + + scrollToTutorial = () => { + if (this.referenceNode) { + this.referenceNode.scrollIntoView({behavior: 'smooth'}) } } - connect = () => { - if (this.state.selectedDevice) { - this.props.connectToHardwareWallet(this.state.selectedDevice) - } - return null - } + renderLearnMore () { + return ( +

    + {this.context.t('learnMore')} + +

    + ) + } - renderConnectToTrezorButton () { - return h( - `button.hw-connect__btn${this.state.selectedDevice === 'trezor' ? '.selected' : ''}`, - { onClick: _ => this.setState({selectedDevice: 'trezor'}) }, - h('img.hw-connect__btn__img', { - src: 'images/trezor-logo.svg', - }) - ) - } - - renderConnectToLedgerButton () { - return h( - `button.hw-connect__btn${this.state.selectedDevice === 'ledger' ? '.selected' : ''}`, - { onClick: _ => this.setState({selectedDevice: 'ledger'}) }, - h('img.hw-connect__btn__img', { - src: 'images/ledger-logo.svg', - }) - ) - } - - renderButtons () { - return ( - h('div', {}, [ - h('div.hw-connect__btn-wrapper', {}, [ - this.renderConnectToLedgerButton(), - this.renderConnectToTrezorButton(), - ]), - h(Button, { - type: 'primary', - large: true, - className: 'hw-connect__connect-btn', - onClick: this.connect, - disabled: !this.state.selectedDevice, - }, this.context.t('connect')), - ]) - ) - } - - renderUnsupportedBrowser () { - return ( - h('div.new-account-connect-form.unsupported-browser', {}, [ - h('div.hw-connect', [ - h('h3.hw-connect__title', {}, this.context.t('browserNotSupported')), - h('p.hw-connect__msg', {}, this.context.t('chromeRequiredForHardwareWallets')), - ]), - h(Button, { - type: 'primary', - large: true, - onClick: () => global.platform.openWindow({ - url: 'https://google.com/chrome', - }), - }, this.context.t('downloadGoogleChrome')), - ]) - ) - } - - renderHeader () { - return ( - h('div.hw-connect__header', {}, [ - h('h3.hw-connect__header__title', {}, this.context.t('hardwareWallets')), - h('p.hw-connect__header__msg', {}, this.context.t('hardwareWalletsMsg')), - ]) - ) - } - - getAffiliateLinks () { - const links = { - trezor: `Trezor`, - ledger: `Ledger`, - } - - const text = this.context.t('orderOneHere') - const response = text.replace('Trezor', links.trezor).replace('Ledger', links.ledger) - - return h('div.hw-connect__get-hw__msg', { dangerouslySetInnerHTML: {__html: response }}) - } - - renderTrezorAffiliateLink () { - return h('div.hw-connect__get-hw', {}, [ - h('p.hw-connect__get-hw__msg', {}, this.context.t('dontHaveAHardwareWallet')), - this.getAffiliateLinks(), - ]) - } - - - scrollToTutorial = () => { - if (this.referenceNode) this.referenceNode.scrollIntoView({behavior: 'smooth'}) - } - - renderLearnMore () { - return ( - h('p.hw-connect__learn-more', { - onClick: this.scrollToTutorial, - }, [ - this.context.t('learnMore'), - h('img.hw-connect__learn-more__arrow', { src: 'images/caret-right.svg'}), - ]) - ) - } - - renderTutorialSteps () { - const steps = [ - { - asset: 'hardware-wallet-step-1', - dimensions: {width: '225px', height: '75px'}, - title: this.context.t('step1HardwareWallet'), - message: this.context.t('step1HardwareWalletMsg'), - }, - { - asset: 'hardware-wallet-step-2', - dimensions: {width: '300px', height: '100px'}, - title: this.context.t('step2HardwareWallet'), - message: this.context.t('step2HardwareWalletMsg'), - }, - { - asset: 'hardware-wallet-step-3', - dimensions: {width: '120px', height: '90px'}, - title: this.context.t('step3HardwareWallet'), - message: this.context.t('step3HardwareWalletMsg'), - }, - ] - - return h('.hw-tutorial', { - ref: node => { this.referenceNode = node }, + renderTutorialSteps () { + const steps = [ + { + asset: 'hardware-wallet-step-1', + dimensions: {width: '225px', height: '75px'}, + title: this.context.t('step1HardwareWallet'), + message: this.context.t('step1HardwareWalletMsg'), }, - steps.map((step) => ( - h('div.hw-connect', {}, [ - h('h3.hw-connect__title', {}, step.title), - h('p.hw-connect__msg', {}, step.message), - h('img.hw-connect__step-asset', { src: `images/${step.asset}.svg`, ...step.dimensions }), - ]) - )) - ) + { + asset: 'hardware-wallet-step-2', + dimensions: {width: '300px', height: '100px'}, + title: this.context.t('step2HardwareWallet'), + message: this.context.t('step2HardwareWalletMsg'), + }, + { + asset: 'hardware-wallet-step-3', + dimensions: {width: '120px', height: '90px'}, + title: this.context.t('step3HardwareWallet'), + message: this.context.t('step3HardwareWalletMsg'), + }, + ] + + return ( +
    { + this.referenceNode = node + }} + > + {steps.map((step, index) => ( +
    +

    {step.title}

    +

    {step.message}

    + +
    + ))} +
    + ) + } + + renderFooter () { + return ( +
    +

    {this.context.t('readyToConnect')}

    + {this.renderButtons()} +

    + {this.context.t('havingTroubleConnecting')} + + {this.context.t('getHelp')} + +

    +
    + ) + } + + renderConnectScreen () { + return ( +
    + {this.renderHeader()} + {this.renderButtons()} + {this.renderTrezorAffiliateLink()} + {this.renderLearnMore()} + {this.renderTutorialSteps()} + {this.renderFooter()} +
    + ) + } + + render () { + if (this.props.browserSupported) { + return this.renderConnectScreen() } - - renderFooter () { - return ( - h('div.hw-connect__footer', {}, [ - h('h3.hw-connect__footer__title', {}, this.context.t('readyToConnect')), - this.renderButtons(), - h('p.hw-connect__footer__msg', {}, [ - this.context.t('havingTroubleConnecting'), - h('a.hw-connect__footer__link', { - href: 'https://support.metamask.io/', - target: '_blank', - }, this.context.t('getHelp')), - ]), - ]) - ) - } - - renderConnectScreen () { - return ( - h('div.new-account-connect-form', {}, [ - this.renderHeader(), - this.renderButtons(), - this.renderTrezorAffiliateLink(), - this.renderLearnMore(), - this.renderTutorialSteps(), - this.renderFooter(), - ]) - ) - } - - render () { - if (this.props.browserSupported) { - return this.renderConnectScreen() - } - return this.renderUnsupportedBrowser() - } -} - -ConnectScreen.propTypes = { - connectToHardwareWallet: PropTypes.func.isRequired, - browserSupported: PropTypes.bool.isRequired, -} - -ConnectScreen.contextTypes = { - t: PropTypes.func, + return this.renderUnsupportedBrowser() + } } module.exports = ConnectScreen diff --git a/ui/app/pages/create-account/connect-hardware/index.js b/ui/app/pages/create-account/connect-hardware/index.js index ff8506142..351699e8a 100644 --- a/ui/app/pages/create-account/connect-hardware/index.js +++ b/ui/app/pages/create-account/connect-hardware/index.js @@ -1,6 +1,5 @@ -const { Component } = require('react') +import React, { Component } from 'react' const PropTypes = require('prop-types') -const h = require('react-hyperscript') const connect = require('react-redux').connect const actions = require('../../../store/actions') const { getMetaMaskAccounts } = require('../../../selectors/selectors') @@ -176,40 +175,53 @@ class ConnectHardwareForm extends Component { renderError () { return this.state.error - ? h('span.error', { style: { margin: '20px 20px 10px', display: 'block', textAlign: 'center' } }, this.state.error) + ? ( + + {this.state.error} + + ) : null } renderContent () { if (!this.state.accounts.length) { - return h(ConnectScreen, { - connectToHardwareWallet: this.connectToHardwareWallet, - browserSupported: this.state.browserSupported, - }) + return ( + + ) } - return h(AccountList, { - onPathChange: this.onPathChange, - selectedPath: this.props.defaultHdPaths[this.state.device], - device: this.state.device, - accounts: this.state.accounts, - selectedAccount: this.state.selectedAccount, - onAccountChange: this.onAccountChange, - network: this.props.network, - getPage: this.getPage, - history: this.props.history, - onUnlockAccount: this.onUnlockAccount, - onForgetDevice: this.onForgetDevice, - onCancel: this.onCancel, - onAccountRestriction: this.onAccountRestriction, - }) + return ( + + ) } render () { - return h('div', [ - this.renderError(), - this.renderContent(), - ]) + return ( +
    + {this.renderError()} + {this.renderContent()} +
    + ) } } diff --git a/ui/app/pages/create-account/create-account.component.js b/ui/app/pages/create-account/create-account.component.js index aa05af975..1f8bc0cc5 100644 --- a/ui/app/pages/create-account/create-account.component.js +++ b/ui/app/pages/create-account/create-account.component.js @@ -23,15 +23,15 @@ export default class CreateAccountPage extends Component { return (
    -
    history.push(NEW_ACCOUNT_ROUTE)}>{ - this.context.t('create') - }
    -
    history.push(IMPORT_ACCOUNT_ROUTE)}>{ - this.context.t('import') - }
    -
    history.push(CONNECT_HARDWARE_ROUTE)}>{ - this.context.t('connect') - }
    +
    history.push(NEW_ACCOUNT_ROUTE)}> + {this.context.t('create')} +
    +
    history.push(IMPORT_ACCOUNT_ROUTE)}> + {this.context.t('import')} +
    +
    history.push(CONNECT_HARDWARE_ROUTE)}> + {this.context.t('connect')} +
    ) } diff --git a/ui/app/pages/create-account/import-account/index.js b/ui/app/pages/create-account/import-account/index.js index 48d8f8838..5ef837715 100644 --- a/ui/app/pages/create-account/import-account/index.js +++ b/ui/app/pages/create-account/import-account/index.js @@ -1,6 +1,5 @@ +import React, { Component } from 'react' const inherits = require('util').inherits -const Component = require('react').Component -const h = require('react-hyperscript') const PropTypes = require('prop-types') const connect = require('react-redux').connect import Select from 'react-select' @@ -35,47 +34,45 @@ AccountImportSubview.prototype.render = function () { const { type } = state return ( - h('div.new-account-import-form', [ - - h('.new-account-import-disclaimer', [ - h('span', this.context.t('importAccountMsg')), - h('span', { - style: { +
    +
    + {this.context.t('importAccountMsg')} + { + }} + onClick={() => { global.platform.openWindow({ url: 'https://metamask.zendesk.com/hc/en-us/articles/360015289932', }) - }, - }, this.context.t('here')), - ]), - - h('div.new-account-import-form__select-section', [ - - h('div.new-account-import-form__select-label', this.context.t('selectType')), - - h(Select, { - className: 'new-account-import-form__select', - name: 'import-type-select', - clearable: false, - value: type || menuItems[0], - options: menuItems.map((type) => { + }} + > + {this.context.t('here')} + +
    +
    +
    + {this.context.t('selectType')} +
    + +
    + + +
    + { + error + ? {error} + : null + } +
    ) } - onLoad (event, file) { - this.setState({file: file, fileContents: event.target.result}) + onLoad (event) { + this.setState({ + fileContents: event.target.result, + }) } createKeyringOnEnter (event) { @@ -89,14 +83,7 @@ class JsonImportSubview extends Component { createNewKeychain () { const { firstAddress, displayWarning, importNewJsonAccount, setSelectedAddress, history } = this.props - const state = this.state - - if (!state) { - const message = this.context.t('validFileImport') - return displayWarning(message) - } - - const { fileContents } = state + const { fileContents } = this.state if (!fileContents) { const message = this.context.t('needImportFile') 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 0cdf25ce9..bbb2b70bd 100644 --- a/ui/app/pages/create-account/import-account/private-key.js +++ b/ui/app/pages/create-account/import-account/private-key.js @@ -1,6 +1,5 @@ +import React, { Component } from 'react' const inherits = require('util').inherits -const Component = require('react').Component -const h = require('react-hyperscript') const { withRouter } = require('react-router-dom') const { compose } = require('recompose') const PropTypes = require('prop-types') @@ -44,47 +43,49 @@ function PrivateKeyImportView () { Component.call(this) } -PrivateKeyImportView.prototype.render = function () { +PrivateKeyImportView.prototype.render = function PrivateKeyImportView () { const { error, displayWarning } = this.props return ( - h('div.new-account-import-form__private-key', [ - - h('span.new-account-create-form__instruction', this.context.t('pastePrivateKey')), - - h('div.new-account-import-form__private-key-password-container', [ - - h('input.new-account-import-form__input-password', { - type: 'password', - id: 'private-key-box', - onKeyPress: e => this.createKeyringOnEnter(e), - }), - - ]), - - h('div.new-account-import-form__buttons', {}, [ - - h(Button, { - type: 'default', - large: true, - className: 'new-account-create-form__button', - onClick: () => { +
    + + {this.context.t('pastePrivateKey')} + +
    + this.createKeyringOnEnter(e)} + /> +
    +
    + + +
    + { + error + ? {error} + : null + } +
    ) } diff --git a/ui/app/pages/create-account/import-account/seed.js b/ui/app/pages/create-account/import-account/seed.js deleted file mode 100644 index 73332f926..000000000 --- a/ui/app/pages/create-account/import-account/seed.js +++ /dev/null @@ -1,35 +0,0 @@ -const inherits = require('util').inherits -const Component = require('react').Component -const h = require('react-hyperscript') -const PropTypes = require('prop-types') -const connect = require('react-redux').connect - -SeedImportSubview.contextTypes = { - t: PropTypes.func, -} - -module.exports = connect(mapStateToProps)(SeedImportSubview) - - -function mapStateToProps () { - return {} -} - -inherits(SeedImportSubview, Component) -function SeedImportSubview () { - Component.call(this) -} - -SeedImportSubview.prototype.render = function () { - return ( - h('div', { - style: { - }, - }, [ - this.context.t('pasteSeed'), - h('textarea'), - h('br'), - h('button', this.context.t('submit')), - ]) - ) -} diff --git a/ui/app/pages/create-account/new-account.component.js b/ui/app/pages/create-account/new-account.component.js index 6dc6419b5..fbb5e128f 100644 --- a/ui/app/pages/create-account/new-account.component.js +++ b/ui/app/pages/create-account/new-account.component.js @@ -49,7 +49,8 @@ export default class NewAccountCreateForm extends Component { {this.context.t('accountName')}
    - this.setState({ newAccountName: event.target.value })} @@ -61,13 +62,17 @@ export default class NewAccountCreateForm extends Component { large className="new-account-create-form__button" onClick={() => history.push(DEFAULT_ROUTE)} - >{this.context.t('cancel')} + > + {this.context.t('cancel')} + + > + {this.context.t('create')} +
    ) 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 1fae5351c..53d4614ad 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 @@ -283,7 +283,8 @@ export default class ImportWithSeedPhrase extends PureComponent { {termsChecked ? : null}
    - I have read and agree to the : null}
    - I have read and agree to the { + const { history, completeOnboarding, completionMetaMetricsName, onboardingInitiator } = this.props + + await completeOnboarding() + this.context.metricsEvent({ + eventOpts: { + category: 'Onboarding', + action: 'Onboarding Complete', + name: completionMetaMetricsName, + }, + }) + + if (onboardingInitiator) { + await returnToOnboardingInitiator(onboardingInitiator) + } + history.push(DEFAULT_ROUTE) } render () { const { t } = this.context - const { history, completeOnboarding, completionMetaMetricsName } = this.props + const { onboardingInitiator } = this.props return (
    @@ -49,7 +73,8 @@ export default class EndOfFlowScreen extends PureComponent { { '• ' + t('endOfFlowMessage7') }
    ) } diff --git a/ui/app/pages/first-time-flow/end-of-flow/end-of-flow.container.js b/ui/app/pages/first-time-flow/end-of-flow/end-of-flow.container.js index 38313806c..2bb9ef15a 100644 --- a/ui/app/pages/first-time-flow/end-of-flow/end-of-flow.container.js +++ b/ui/app/pages/first-time-flow/end-of-flow/end-of-flow.container.js @@ -1,21 +1,22 @@ import { connect } from 'react-redux' import EndOfFlow from './end-of-flow.component' import { setCompletedOnboarding } from '../../../store/actions' +import { getOnboardingInitiator } from '../first-time-flow.selectors' const firstTimeFlowTypeNameMap = { create: 'New Wallet Created', 'import': 'New Wallet Imported', } -const mapStateToProps = ({ metamask }) => { - const { firstTimeFlowType } = metamask +const mapStateToProps = (state) => { + const { metamask: { firstTimeFlowType } } = state return { completionMetaMetricsName: firstTimeFlowTypeNameMap[firstTimeFlowType], + onboardingInitiator: getOnboardingInitiator(state), } } - const mapDispatchToProps = dispatch => { return { completeOnboarding: () => dispatch(setCompletedOnboarding()), diff --git a/ui/app/pages/first-time-flow/end-of-flow/index.scss b/ui/app/pages/first-time-flow/end-of-flow/index.scss index d7eb4513b..de603fce4 100644 --- a/ui/app/pages/first-time-flow/end-of-flow/index.scss +++ b/ui/app/pages/first-time-flow/end-of-flow/index.scss @@ -50,4 +50,4 @@ font-size: 80px; margin-top: 70px; } -} \ No newline at end of file +} diff --git a/ui/app/pages/first-time-flow/first-time-flow.selectors.js b/ui/app/pages/first-time-flow/first-time-flow.selectors.js index e6cd5a84a..74cad5e12 100644 --- a/ui/app/pages/first-time-flow/first-time-flow.selectors.js +++ b/ui/app/pages/first-time-flow/first-time-flow.selectors.js @@ -4,12 +4,6 @@ import { DEFAULT_ROUTE, } from '../../helpers/constants/routes' -const selectors = { - getFirstTimeFlowTypeRoute, -} - -module.exports = selectors - function getFirstTimeFlowTypeRoute (state) { const { firstTimeFlowType } = state.metamask @@ -24,3 +18,25 @@ function getFirstTimeFlowTypeRoute (state) { return nextRoute } + +const getOnboardingInitiator = (state) => { + const { onboardingTabs } = state.metamask + + if (!onboardingTabs || Object.keys(onboardingTabs).length !== 1) { + return null + } + + const location = Object.keys(onboardingTabs)[0] + const tabId = onboardingTabs[location] + return { + location, + tabId, + } +} + +const selectors = { + getFirstTimeFlowTypeRoute, + getOnboardingInitiator, +} + +module.exports = selectors 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 ad1ffbf42..14c85b923 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 @@ -142,7 +142,8 @@ export default class MetaMetricsOptIn extends Component { disabled={false} />
    - This data is aggregated and is therefore anonymous for the purposes of General Data Protection Regulation (EU) 2016/679. For more information in relation to our privacy practices, please see our { + const tab = await (new Promise((resolve) => { + extension.tabs.update(onboardingInitiator.tabId, { active: true }, (tab) => { + if (tab) { + resolve(tab) + } else { + // silence console message about unchecked error + if (extension.runtime.lastError) { + log.debug(extension.runtime.lastError) + } + resolve() + } + }) + })) + + if (!tab) { + // this case can happen if the tab was closed since being checked with `extension.tabs.get` + log.warn(`Setting current tab to onboarding initator has failed; falling back to redirect`) + window.location.assign(onboardingInitiator.location) + } else { + window.close() + } +} + +export const returnToOnboardingInitiator = async (onboardingInitiator) => { + const tab = await (new Promise((resolve) => { + extension.tabs.get(onboardingInitiator.tabId, (tab) => { + if (tab) { + resolve(tab) + } else { + // silence console message about unchecked error + if (extension.runtime.lastError) { + log.debug(extension.runtime.lastError) + } + resolve() + } + }) + })) + + if (tab) { + await returnToOnboardingInitiatorTab(onboardingInitiator) + } else { + window.location.assign(onboardingInitiator.location) + } +} 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 index 7e0652f8f..e9bef631e 100644 --- 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 @@ -13,7 +13,7 @@ class DraggableSeed extends Component { isOver: PropTypes.bool, canDrop: PropTypes.bool, // Own Props - onClick: PropTypes.func.isRequired, + onClick: PropTypes.func, setHoveringIndex: PropTypes.func.isRequired, index: PropTypes.number, draggingSeedIndex: PropTypes.number, @@ -25,6 +25,7 @@ class DraggableSeed extends Component { static defaultProps = { className: '', + onClick: undefined, } componentWillReceiveProps (nextProps) { diff --git a/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.scss b/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.scss index dfe9868cf..583d92a0b 100644 --- a/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.scss +++ b/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.scss @@ -60,7 +60,7 @@ } button { - margin-top: 0xp; + margin-top: 0px; } &__buttons { 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 4144fb878..922685059 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 @@ -3,8 +3,10 @@ import PropTypes from 'prop-types' import classnames from 'classnames' import LockIcon from '../../../../components/ui/lock-icon' import Button from '../../../../components/ui/button' +import Snackbar from '../../../../components/ui/snackbar' import { INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE, DEFAULT_ROUTE } from '../../../../helpers/constants/routes' import { exportAsFile } from '../../../../helpers/utils/util' +import { returnToOnboardingInitiator } from '../../onboarding-initiator-util' export default class RevealSeedPhrase extends PureComponent { static contextTypes = { @@ -17,6 +19,10 @@ export default class RevealSeedPhrase extends PureComponent { seedPhrase: PropTypes.string, setSeedPhraseBackedUp: PropTypes.func, setCompletedOnboarding: PropTypes.func, + onboardingInitiator: PropTypes.exact({ + location: PropTypes.string, + tabId: PropTypes.number, + }), } state = { @@ -27,8 +33,7 @@ export default class RevealSeedPhrase extends PureComponent { exportAsFile('MetaMask Secret Backup Phrase', this.props.seedPhrase, 'text/plain') } - handleNext = event => { - event.preventDefault() + handleNext = () => { const { isShowingSeedPhrase } = this.state const { history } = this.props @@ -47,9 +52,8 @@ export default class RevealSeedPhrase extends PureComponent { history.push(INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE) } - handleSkip = event => { - event.preventDefault() - const { history, setSeedPhraseBackedUp, setCompletedOnboarding } = this.props + handleSkip = async () => { + const { history, setSeedPhraseBackedUp, setCompletedOnboarding, onboardingInitiator } = this.props this.context.metricsEvent({ eventOpts: { @@ -59,10 +63,12 @@ export default class RevealSeedPhrase extends PureComponent { }, }) - Promise.all([setCompletedOnboarding(), setSeedPhraseBackedUp(false)]) - .then(() => { - history.push(DEFAULT_ROUTE) - }) + await Promise.all([setCompletedOnboarding(), setSeedPhraseBackedUp(false)]) + + if (onboardingInitiator) { + await returnToOnboardingInitiator(onboardingInitiator) + } + history.push(DEFAULT_ROUTE) } renderSecretWordsContainer () { @@ -72,10 +78,12 @@ export default class RevealSeedPhrase extends PureComponent { return (
    - ) } diff --git a/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.container.js b/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.container.js index 7ada36afc..11a26fb6d 100644 --- a/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.container.js +++ b/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.container.js @@ -4,6 +4,13 @@ import { setCompletedOnboarding, setSeedPhraseBackedUp, } from '../../../../store/actions' +import { getOnboardingInitiator } from '../../first-time-flow.selectors' + +const mapStateToProps = (state) => { + return { + onboardingInitiator: getOnboardingInitiator(state), + } +} const mapDispatchToProps = dispatch => { return { @@ -12,4 +19,4 @@ const mapDispatchToProps = dispatch => { } } -export default connect(null, mapDispatchToProps)(RevealSeedPhrase) +export default connect(mapStateToProps, mapDispatchToProps)(RevealSeedPhrase) diff --git a/ui/app/pages/home/home.component.js b/ui/app/pages/home/home.component.js index f08a0bb47..fe76a139f 100644 --- a/ui/app/pages/home/home.component.js +++ b/ui/app/pages/home/home.component.js @@ -7,13 +7,13 @@ import HomeNotification from '../../components/app/home-notification' import MultipleNotifications from '../../components/app/multiple-notifications' import WalletView from '../../components/app/wallet-view' import TransactionView from '../../components/app/transaction-view' -import ProviderApproval from '../provider-approval' import { RESTORE_VAULT_ROUTE, CONFIRM_TRANSACTION_ROUTE, CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE, INITIALIZE_BACKUP_SEED_PHRASE_ROUTE, + CONNECT_ROUTE, } from '../../helpers/constants/routes' export default class Home extends PureComponent { @@ -21,18 +21,11 @@ export default class Home extends PureComponent { t: PropTypes.func, } - static defaultProps = { - unsetMigratedPrivacyMode: null, - } - static propTypes = { history: PropTypes.object, forgottenPassword: PropTypes.bool, suggestedTokens: PropTypes.object, unconfirmedTransactionsCount: PropTypes.number, - providerRequests: PropTypes.array, - showPrivacyModeNotification: PropTypes.bool.isRequired, - unsetMigratedPrivacyMode: PropTypes.func, shouldShowSeedPhraseReminder: PropTypes.bool, isPopup: PropTypes.bool, threeBoxSynced: PropTypes.bool, @@ -43,14 +36,20 @@ export default class Home extends PureComponent { restoreFromThreeBox: PropTypes.func, setShowRestorePromptToFalse: PropTypes.func, threeBoxLastUpdated: PropTypes.number, + hasPermissionRequests: PropTypes.bool, } componentWillMount () { const { history, unconfirmedTransactionsCount = 0, + hasPermissionRequests, } = this.props + if (hasPermissionRequests) { + history.push(CONNECT_ROUTE) + } + if (unconfirmedTransactionsCount > 0) { history.push(CONFIRM_TRANSACTION_ROUTE) } @@ -84,10 +83,7 @@ export default class Home extends PureComponent { const { t } = this.context const { forgottenPassword, - providerRequests, history, - showPrivacyModeNotification, - unsetMigratedPrivacyMode, shouldShowSeedPhraseReminder, isPopup, selectedAddress, @@ -102,11 +98,6 @@ export default class Home extends PureComponent { return } - if (providerRequests && providerRequests.length > 0) { - return ( - - ) - } return (
    @@ -118,58 +109,45 @@ export default class Home extends PureComponent { ? ( - { - showPrivacyModeNotification - ? { - unsetMigratedPrivacyMode() - window.open('https://medium.com/metamask/42549d4870fa', '_blank', 'noopener') - }} - ignoreText={t('dismiss')} - onIgnore={() => { - unsetMigratedPrivacyMode() - }} - key="home-privacyModeDefault" - /> - : null - } { shouldShowSeedPhraseReminder - ? { - if (isPopup) { - global.platform.openExtensionInBrowser(INITIALIZE_BACKUP_SEED_PHRASE_ROUTE) - } else { - history.push(INITIALIZE_BACKUP_SEED_PHRASE_ROUTE) - } - }} - infoText={t('backupApprovalInfo')} - key="home-backupApprovalNotice" - /> + ? ( + { + if (isPopup) { + global.platform.openExtensionInBrowser(INITIALIZE_BACKUP_SEED_PHRASE_ROUTE) + } else { + history.push(INITIALIZE_BACKUP_SEED_PHRASE_ROUTE) + } + }} + infoText={t('backupApprovalInfo')} + key="home-backupApprovalNotice" + /> + ) : null } { threeBoxLastUpdated && showRestorePrompt - ? { - restoreFromThreeBox(selectedAddress) - .then(() => { - turnThreeBoxSyncingOn() - }) - }} - onIgnore={() => { - setShowRestorePromptToFalse() - }} - key="home-privacyModeDefault" - /> + ? ( + { + restoreFromThreeBox(selectedAddress) + .then(() => { + turnThreeBoxSyncingOn() + }) + }} + onIgnore={() => { + setShowRestorePromptToFalse() + }} + key="home-privacyModeDefault" + /> + ) : null } diff --git a/ui/app/pages/home/home.container.js b/ui/app/pages/home/home.container.js index 1bac780af..067fc62eb 100644 --- a/ui/app/pages/home/home.container.js +++ b/ui/app/pages/home/home.container.js @@ -3,9 +3,8 @@ import { compose } from 'recompose' import { connect } from 'react-redux' import { withRouter } from 'react-router-dom' import { unconfirmedTransactionsCountSelector } from '../../selectors/confirm-transaction' -import { getCurrentEthBalance } from '../../selectors/selectors' +import { getCurrentEthBalance, hasPermissionRequests } from '../../selectors/selectors' import { - unsetMigratedPrivacyMode, restoreFromThreeBox, turnThreeBoxSyncingOn, getThreeBoxLastUpdated, @@ -16,11 +15,9 @@ import { getEnvironmentType } from '../../../../app/scripts/lib/util' import { ENVIRONMENT_TYPE_POPUP } from '../../../../app/scripts/lib/enums' const mapStateToProps = state => { - const { metamask, appState } = state + const { activeTab, metamask, appState } = state const { suggestedTokens, - providerRequests, - migratedPrivacyMode, seedPhraseBackedUp, tokens, threeBoxSynced, @@ -36,19 +33,18 @@ const mapStateToProps = state => { forgottenPassword, suggestedTokens, unconfirmedTransactionsCount: unconfirmedTransactionsCountSelector(state), - providerRequests, - showPrivacyModeNotification: migratedPrivacyMode, + activeTab, shouldShowSeedPhraseReminder: !seedPhraseBackedUp && (parseInt(accountBalance, 16) > 0 || tokens.length > 0), isPopup, threeBoxSynced, showRestorePrompt, selectedAddress, threeBoxLastUpdated, + hasPermissionRequests: hasPermissionRequests(state), } } const mapDispatchToProps = (dispatch) => ({ - unsetMigratedPrivacyMode: () => dispatch(unsetMigratedPrivacyMode()), turnThreeBoxSyncingOn: () => dispatch(turnThreeBoxSyncingOn()), setupThreeBox: () => { dispatch(getThreeBoxLastUpdated()) diff --git a/ui/app/pages/index.scss b/ui/app/pages/index.scss index d79b7c28d..deb6bce45 100644 --- a/ui/app/pages/index.scss +++ b/ui/app/pages/index.scss @@ -13,3 +13,7 @@ @import 'keychains/index'; @import 'confirm-approve/index'; + +@import 'permissions-connect/index'; + +@import 'connected-sites/index'; diff --git a/ui/app/pages/keychains/reveal-seed.js b/ui/app/pages/keychains/reveal-seed.js index e83e3fd98..0e842594f 100644 --- a/ui/app/pages/keychains/reveal-seed.js +++ b/ui/app/pages/keychains/reveal-seed.js @@ -1,7 +1,6 @@ -const { Component } = require('react') +import React, { Component } from 'react' const { connect } = require('react-redux') const PropTypes = require('prop-types') -const h = require('react-hyperscript') const classnames = require('classnames') const { requestRevealSeedWords } = require('../../store/actions') @@ -14,15 +13,11 @@ const PASSWORD_PROMPT_SCREEN = 'PASSWORD_PROMPT_SCREEN' const REVEAL_SEED_SCREEN = 'REVEAL_SEED_SCREEN' class RevealSeedPage extends Component { - constructor (props) { - super(props) - - this.state = { - screen: PASSWORD_PROMPT_SCREEN, - password: '', - seedWords: null, - error: null, - } + state = { + screen: PASSWORD_PROMPT_SCREEN, + password: '', + seedWords: null, + error: null, } componentDidMount () { @@ -42,15 +37,17 @@ class RevealSeedPage extends Component { renderWarning () { return ( - h('.page-container__warning-container', [ - h('img.page-container__warning-icon', { - src: 'images/warning.svg', - }), - h('.page-container__warning-message', [ - h('.page-container__warning-title', [this.context.t('revealSeedWordsWarningTitle')]), - h('div', [this.context.t('revealSeedWordsWarning')]), - ]), - ]) +
    + +
    +
    + {this.context.t('revealSeedWordsWarningTitle')} +
    +
    + {this.context.t('revealSeedWordsWarning')} +
    +
    +
    ) } @@ -64,24 +61,25 @@ class RevealSeedPage extends Component { const { t } = this.context return ( - h('form', { - onSubmit: event => this.handleSubmit(event), - }, [ - h('label.input-label', { - htmlFor: 'password-box', - }, t('enterPasswordContinue')), - h('.input-group', [ - h('input.form-control', { - type: 'password', - placeholder: t('password'), - id: 'password-box', - value: this.state.password, - onChange: event => this.setState({ password: event.target.value }), - className: classnames({ 'form-control--error': this.state.error }), - }), - ]), - this.state.error && h('.reveal-seed__error', this.state.error), - ]) +
    this.handleSubmit(event)}> + +
    + this.setState({ password: event.target.value })} + className={classnames('form-control', { 'form-control--error': this.state.error })} + /> +
    + { + this.state.error && ( +
    + {this.state.error} +
    + )} +
    ) } @@ -89,13 +87,10 @@ class RevealSeedPage extends Component { const { t } = this.context return ( - h('div', [ - h('label.reveal-seed__label', t('yourPrivateSeedPhrase')), - h(ExportTextContainer, { - text: this.state.seedWords, - filename: t('metamaskSeedWords'), - }), - ]) +
    + + +
    ) } @@ -107,54 +102,64 @@ class RevealSeedPage extends Component { renderPasswordPromptFooter () { return ( - h('.page-container__footer', [ - h('header', [ - h(Button, { - type: 'default', - large: true, - className: 'page-container__footer-button', - onClick: () => this.props.history.push(DEFAULT_ROUTE), - }, this.context.t('cancel')), - h(Button, { - type: 'secondary', - large: true, - className: 'page-container__footer-button', - onClick: event => this.handleSubmit(event), - disabled: this.state.password === '', - }, this.context.t('next')), - ]), - ]) +
    +
    + + +
    +
    ) } renderRevealSeedFooter () { return ( - h('.page-container__footer', [ - h(Button, { - type: 'default', - large: true, - className: 'page-container__footer-button', - onClick: () => this.props.history.push(DEFAULT_ROUTE), - }, this.context.t('close')), - ]) +
    + +
    ) } render () { return ( - h('.page-container', [ - h('.page-container__header', [ - h('.page-container__title', this.context.t('revealSeedWordsTitle')), - h('.page-container__subtitle', this.context.t('revealSeedWordsDescription')), - ]), - h('.page-container__content', [ - this.renderWarning(), - h('.reveal-seed__content', [ - this.renderContent(), - ]), - ]), - this.renderFooter(), - ]) +
    +
    +
    + {this.context.t('revealSeedWordsTitle')} +
    +
    + {this.context.t('revealSeedWordsDescription')} +
    +
    +
    + {this.renderWarning()} +
    + {this.renderContent()} +
    +
    + {this.renderFooter()} +
    ) } } diff --git a/ui/app/pages/mobile-sync/index.js b/ui/app/pages/mobile-sync/index.js index bd2385808..c7df5ede5 100644 --- a/ui/app/pages/mobile-sync/index.js +++ b/ui/app/pages/mobile-sync/index.js @@ -1,415 +1 @@ -const { Component } = require('react') -const { connect } = require('react-redux') -const PropTypes = require('prop-types') -const h = require('react-hyperscript') -const classnames = require('classnames') -const PubNub = require('pubnub') - -const { requestRevealSeedWords, fetchInfoToSync } = require('../../store/actions') -const { DEFAULT_ROUTE } = require('../../helpers/constants/routes') -const actions = require('../../store/actions') - -const qrCode = require('qrcode-generator') - -import Button from '../../components/ui/button' -import LoadingScreen from '../../components/ui/loading-screen' - -const PASSWORD_PROMPT_SCREEN = 'PASSWORD_PROMPT_SCREEN' -const REVEAL_SEED_SCREEN = 'REVEAL_SEED_SCREEN' -const KEYS_GENERATION_TIME = 30000 - -class MobileSyncPage extends Component { - static propTypes = { - history: PropTypes.object, - selectedAddress: PropTypes.string, - displayWarning: PropTypes.func, - fetchInfoToSync: PropTypes.func, - requestRevealSeedWords: PropTypes.func, - } - - constructor (props) { - super(props) - - this.state = { - screen: PASSWORD_PROMPT_SCREEN, - password: '', - seedWords: null, - error: null, - syncing: false, - completed: false, - channelName: undefined, - cipherKey: undefined, - } - - this.syncing = false - } - - componentDidMount () { - const passwordBox = document.getElementById('password-box') - if (passwordBox) { - passwordBox.focus() - } - } - - handleSubmit (event) { - event.preventDefault() - this.setState({ seedWords: null, error: null }) - this.props.requestRevealSeedWords(this.state.password) - .then(seedWords => { - this.startKeysGeneration() - this.setState({ seedWords, screen: REVEAL_SEED_SCREEN }) - }) - .catch(error => this.setState({ error: error.message })) - } - - startKeysGeneration () { - this.handle && clearTimeout(this.handle) - this.disconnectWebsockets() - this.generateCipherKeyAndChannelName() - this.initWebsockets() - this.handle = setTimeout(() => { - this.startKeysGeneration() - }, KEYS_GENERATION_TIME) - } - - generateCipherKeyAndChannelName () { - this.cipherKey = `${this.props.selectedAddress.substr(-4)}-${PubNub.generateUUID()}` - this.channelName = `mm-${PubNub.generateUUID()}` - this.setState({cipherKey: this.cipherKey, channelName: this.channelName}) - } - - initWithCipherKeyAndChannelName (cipherKey, channelName) { - this.cipherKey = cipherKey - this.channelName = channelName - } - - initWebsockets () { - // Make sure there are no existing listeners - this.disconnectWebsockets() - - this.pubnub = new PubNub({ - subscribeKey: process.env.PUBNUB_SUB_KEY, - publishKey: process.env.PUBNUB_PUB_KEY, - cipherKey: this.cipherKey, - ssl: true, - }) - - this.pubnubListener = { - message: (data) => { - const {channel, message} = data - // handle message - if (channel !== this.channelName || !message) { - return false - } - - if (message.event === 'start-sync') { - this.startSyncing() - } else if (message.event === 'connection-info') { - this.handle && clearTimeout(this.handle) - this.disconnectWebsockets() - this.initWithCipherKeyAndChannelName(message.cipher, message.channel) - this.initWebsockets() - } else if (message.event === 'end-sync') { - this.disconnectWebsockets() - this.setState({syncing: false, completed: true}) - } - }, - } - - this.pubnub.addListener(this.pubnubListener) - - this.pubnub.subscribe({ - channels: [this.channelName], - withPresence: false, - }) - - } - - disconnectWebsockets () { - if (this.pubnub && this.pubnubListener) { - this.pubnub.removeListener(this.pubnubListener) - } - } - - // Calculating a PubNub Message Payload Size. - calculatePayloadSize (channel, message) { - return encodeURIComponent( - channel + JSON.stringify(message) - ).length + 100 - } - - chunkString (str, size) { - const numChunks = Math.ceil(str.length / size) - const chunks = new Array(numChunks) - for (let i = 0, o = 0; i < numChunks; ++i, o += size) { - chunks[i] = str.substr(o, size) - } - return chunks - } - - notifyError (errorMsg) { - return new Promise((resolve, reject) => { - this.pubnub.publish( - { - message: { - event: 'error-sync', - data: errorMsg, - }, - channel: this.channelName, - sendByPost: false, // true to send via post - storeInHistory: false, - }, - (status, response) => { - if (!status.error) { - resolve() - } else { - reject(response) - } - }) - }) - } - - async startSyncing () { - if (this.syncing) return false - this.syncing = true - this.setState({syncing: true}) - - const { accounts, network, preferences, transactions } = await this.props.fetchInfoToSync() - - const allDataStr = JSON.stringify({ - accounts, - network, - preferences, - transactions, - udata: { - pwd: this.state.password, - seed: this.state.seedWords, - }, - }) - - const chunks = this.chunkString(allDataStr, 17000) - const totalChunks = chunks.length - try { - for (let i = 0; i < totalChunks; i++) { - await this.sendMessage(chunks[i], i + 1, totalChunks) - } - } catch (e) { - this.props.displayWarning('Sync failed :(') - this.setState({syncing: false}) - this.syncing = false - this.notifyError(e.toString()) - } - } - - sendMessage (data, pkg, count) { - return new Promise((resolve, reject) => { - this.pubnub.publish( - { - message: { - event: 'syncing-data', - data, - totalPkg: count, - currentPkg: pkg, - }, - channel: this.channelName, - sendByPost: false, // true to send via post - storeInHistory: false, - }, - (status, response) => { - if (!status.error) { - resolve() - } else { - reject(response) - } - } - ) - }) - } - - - componentWillUnmount () { - this.disconnectWebsockets() - } - - renderWarning (text) { - return ( - h('.page-container__warning-container', [ - h('.page-container__warning-message', [ - h('div', [text]), - ]), - ]) - ) - } - - renderContent () { - const { t } = this.context - - if (this.state.syncing) { - return h(LoadingScreen, {loadingMessage: 'Sync in progress'}) - } - - if (this.state.completed) { - return h('div.reveal-seed__content', {}, - h('label.reveal-seed__label', { - style: { - width: '100%', - textAlign: 'center', - }, - }, t('syncWithMobileComplete')), - ) - } - - return this.state.screen === PASSWORD_PROMPT_SCREEN - ? h('div', {}, [ - this.renderWarning(this.context.t('mobileSyncText')), - h('.reveal-seed__content', [ - this.renderPasswordPromptContent(), - ]), - ]) - : h('div', {}, [ - this.renderWarning(this.context.t('syncWithMobileBeCareful')), - h('.reveal-seed__content', [ this.renderRevealSeedContent() ]), - ]) - } - - renderPasswordPromptContent () { - const { t } = this.context - - return ( - h('form', { - onSubmit: event => this.handleSubmit(event), - }, [ - h('label.input-label', { - htmlFor: 'password-box', - }, t('enterPasswordContinue')), - h('.input-group', [ - h('input.form-control', { - type: 'password', - placeholder: t('password'), - id: 'password-box', - value: this.state.password, - onChange: event => this.setState({ password: event.target.value }), - className: classnames({ 'form-control--error': this.state.error }), - }), - ]), - this.state.error && h('.reveal-seed__error', this.state.error), - ]) - ) - } - - renderRevealSeedContent () { - - const qrImage = qrCode(0, 'M') - qrImage.addData(`metamask-sync:${this.state.channelName}|@|${this.state.cipherKey}`) - qrImage.make() - - const { t } = this.context - return ( - h('div', [ - h('label.reveal-seed__label', { - style: { - width: '100%', - textAlign: 'center', - }, - }, t('syncWithMobileScanThisCode')), - h('.div.qr-wrapper', { - style: { - display: 'flex', - justifyContent: 'center', - }, - dangerouslySetInnerHTML: { - __html: qrImage.createTableTag(4), - }, - }), - ]) - ) - } - - renderFooter () { - return this.state.screen === PASSWORD_PROMPT_SCREEN - ? this.renderPasswordPromptFooter() - : this.renderRevealSeedFooter() - } - - renderPasswordPromptFooter () { - return ( - h('div.new-account-import-form__buttons', {style: {padding: 30}}, [ - - h(Button, { - type: 'default', - large: true, - className: 'new-account-create-form__button', - onClick: () => this.props.history.push(DEFAULT_ROUTE), - }, this.context.t('cancel')), - - h(Button, { - type: 'secondary', - large: true, - className: 'new-account-create-form__button', - onClick: event => this.handleSubmit(event), - disabled: this.state.password === '', - }, this.context.t('next')), - ]) - ) - } - - renderRevealSeedFooter () { - return ( - h('.page-container__footer', {style: {padding: 30}}, [ - h(Button, { - type: 'default', - large: true, - className: 'page-container__footer-button', - onClick: () => this.props.history.push(DEFAULT_ROUTE), - }, this.context.t('close')), - ]) - ) - } - - render () { - return ( - h('.page-container', [ - h('.page-container__header', [ - h('.page-container__title', this.context.t('syncWithMobileTitle')), - this.state.screen === PASSWORD_PROMPT_SCREEN ? h('.page-container__subtitle', this.context.t('syncWithMobileDesc')) : null, - this.state.screen === PASSWORD_PROMPT_SCREEN ? h('.page-container__subtitle', this.context.t('syncWithMobileDescNewUsers')) : null, - ]), - h('.page-container__content', [ - this.renderContent(), - ]), - this.renderFooter(), - ]) - ) - } -} - -MobileSyncPage.propTypes = { - requestRevealSeedWords: PropTypes.func, - fetchInfoToSync: PropTypes.func, - history: PropTypes.object, -} - -MobileSyncPage.contextTypes = { - t: PropTypes.func, -} - -const mapDispatchToProps = dispatch => { - return { - requestRevealSeedWords: password => dispatch(requestRevealSeedWords(password)), - fetchInfoToSync: () => dispatch(fetchInfoToSync()), - displayWarning: (message) => dispatch(actions.displayWarning(message || null)), - } - -} - -const mapStateToProps = state => { - const { - metamask: { selectedAddress }, - } = state - - return { - selectedAddress, - } -} - -module.exports = connect(mapStateToProps, mapDispatchToProps)(MobileSyncPage) +export { default } from './mobile-sync.container' diff --git a/ui/app/pages/mobile-sync/mobile-sync.component.js b/ui/app/pages/mobile-sync/mobile-sync.component.js new file mode 100644 index 000000000..9510afa80 --- /dev/null +++ b/ui/app/pages/mobile-sync/mobile-sync.component.js @@ -0,0 +1,436 @@ +import React, { Component } from 'react' + +const PropTypes = require('prop-types') +const classnames = require('classnames') +const PubNub = require('pubnub') +const qrCode = require('qrcode-generator') + +const { DEFAULT_ROUTE } = require('../../helpers/constants/routes') + +import Button from '../../components/ui/button' +import LoadingScreen from '../../components/ui/loading-screen' + +const PASSWORD_PROMPT_SCREEN = 'PASSWORD_PROMPT_SCREEN' +const REVEAL_SEED_SCREEN = 'REVEAL_SEED_SCREEN' +const KEYS_GENERATION_TIME = 30000 + +export default class MobileSyncPage extends Component { + static contextTypes = { + t: PropTypes.func, + } + + static propTypes = { + history: PropTypes.object.isRequired, + selectedAddress: PropTypes.string.isRequired, + displayWarning: PropTypes.func.isRequired, + fetchInfoToSync: PropTypes.func.isRequired, + requestRevealSeedWords: PropTypes.func.isRequired, + } + + + state = { + screen: PASSWORD_PROMPT_SCREEN, + password: '', + seedWords: null, + error: null, + syncing: false, + completed: false, + channelName: undefined, + cipherKey: undefined, + } + + syncing = false + + componentDidMount () { + const passwordBox = document.getElementById('password-box') + if (passwordBox) { + passwordBox.focus() + } + } + + handleSubmit (event) { + event.preventDefault() + this.setState({ seedWords: null, error: null }) + this.props.requestRevealSeedWords(this.state.password) + .then(seedWords => { + this.startKeysGeneration() + this.setState({ seedWords, screen: REVEAL_SEED_SCREEN }) + }) + .catch(error => this.setState({ error: error.message })) + } + + startKeysGeneration () { + this.handle && clearTimeout(this.handle) + this.disconnectWebsockets() + this.generateCipherKeyAndChannelName() + this.initWebsockets() + this.handle = setTimeout(() => { + this.startKeysGeneration() + }, KEYS_GENERATION_TIME) + } + + generateCipherKeyAndChannelName () { + this.cipherKey = `${this.props.selectedAddress.substr(-4)}-${PubNub.generateUUID()}` + this.channelName = `mm-${PubNub.generateUUID()}` + this.setState({cipherKey: this.cipherKey, channelName: this.channelName}) + } + + initWithCipherKeyAndChannelName (cipherKey, channelName) { + this.cipherKey = cipherKey + this.channelName = channelName + } + + initWebsockets () { + // Make sure there are no existing listeners + this.disconnectWebsockets() + + this.pubnub = new PubNub({ + subscribeKey: process.env.PUBNUB_SUB_KEY, + publishKey: process.env.PUBNUB_PUB_KEY, + cipherKey: this.cipherKey, + ssl: true, + }) + + this.pubnubListener = { + message: (data) => { + const {channel, message} = data + // handle message + if (channel !== this.channelName || !message) { + return false + } + + if (message.event === 'start-sync') { + this.startSyncing() + } else if (message.event === 'connection-info') { + this.handle && clearTimeout(this.handle) + this.disconnectWebsockets() + this.initWithCipherKeyAndChannelName(message.cipher, message.channel) + this.initWebsockets() + } else if (message.event === 'end-sync') { + this.disconnectWebsockets() + this.setState({syncing: false, completed: true}) + } + }, + } + + this.pubnub.addListener(this.pubnubListener) + + this.pubnub.subscribe({ + channels: [this.channelName], + withPresence: false, + }) + + } + + disconnectWebsockets () { + if (this.pubnub && this.pubnubListener) { + this.pubnub.removeListener(this.pubnubListener) + } + } + + // Calculating a PubNub Message Payload Size. + calculatePayloadSize (channel, message) { + return encodeURIComponent( + channel + JSON.stringify(message) + ).length + 100 + } + + chunkString (str, size) { + const numChunks = Math.ceil(str.length / size) + const chunks = new Array(numChunks) + for (let i = 0, o = 0; i < numChunks; ++i, o += size) { + chunks[i] = str.substr(o, size) + } + return chunks + } + + notifyError (errorMsg) { + return new Promise((resolve, reject) => { + this.pubnub.publish( + { + message: { + event: 'error-sync', + data: errorMsg, + }, + channel: this.channelName, + sendByPost: false, // true to send via post + storeInHistory: false, + }, + (status, response) => { + if (!status.error) { + resolve() + } else { + reject(response) + } + }) + }) + } + + async startSyncing () { + if (this.syncing) { + return false + } + this.syncing = true + this.setState({syncing: true}) + + const { accounts, network, preferences, transactions } = await this.props.fetchInfoToSync() + + const allDataStr = JSON.stringify({ + accounts, + network, + preferences, + transactions, + udata: { + pwd: this.state.password, + seed: this.state.seedWords, + }, + }) + + const chunks = this.chunkString(allDataStr, 17000) + const totalChunks = chunks.length + try { + for (let i = 0; i < totalChunks; i++) { + await this.sendMessage(chunks[i], i + 1, totalChunks) + } + } catch (e) { + this.props.displayWarning('Sync failed :(') + this.setState({syncing: false}) + this.syncing = false + this.notifyError(e.toString()) + } + } + + sendMessage (data, pkg, count) { + return new Promise((resolve, reject) => { + this.pubnub.publish( + { + message: { + event: 'syncing-data', + data, + totalPkg: count, + currentPkg: pkg, + }, + channel: this.channelName, + sendByPost: false, // true to send via post + storeInHistory: false, + }, + (status, response) => { + if (!status.error) { + resolve() + } else { + reject(response) + } + } + ) + }) + } + + + componentWillUnmount () { + this.disconnectWebsockets() + } + + renderWarning (text) { + return ( +
    +
    +
    {text}
    +
    +
    + ) + } + + renderContent () { + const { syncing, completed, screen } = this.state + const { t } = this.context + + if (syncing) { + return ( + + ) + } + + if (completed) { + return ( +
    + +
    + ) + } + + return screen === PASSWORD_PROMPT_SCREEN + ? ( +
    + {this.renderWarning(this.context.t('mobileSyncText'))} +
    + {this.renderPasswordPromptContent()} +
    +
    + ) + : ( +
    + {this.renderWarning(this.context.t('syncWithMobileBeCareful'))} +
    + {this.renderRevealSeedContent()} +
    +
    + ) + } + + renderPasswordPromptContent () { + const { t } = this.context + + return ( +
    this.handleSubmit(event)}> + +
    + this.setState({ password: event.target.value })} + className={classnames('form-control', { + 'form-control--error': this.state.error, + })} + /> +
    + {this.state.error && ( +
    + {this.state.error} +
    + )} +
    + ) + } + + renderRevealSeedContent () { + const qrImage = qrCode(0, 'M') + qrImage.addData(`metamask-sync:${this.state.channelName}|@|${this.state.cipherKey}`) + qrImage.make() + + const { t } = this.context + return ( +
    + +
    +
    + ) + } + + renderFooter () { + return this.state.screen === PASSWORD_PROMPT_SCREEN + ? this.renderPasswordPromptFooter() + : this.renderRevealSeedFooter() + } + + renderPasswordPromptFooter () { + const { t } = this.context + const { history } = this.props + const { password } = this.state + + return ( +
    + + +
    + ) + } + + renderRevealSeedFooter () { + const { t } = this.context + const { history } = this.props + + return ( +
    + +
    + ) + } + + render () { + const { t } = this.context + const { screen } = this.state + + return ( +
    +
    +
    + {t('syncWithMobileTitle')} +
    + { + screen === PASSWORD_PROMPT_SCREEN + ? ( +
    + {t('syncWithMobileDesc')} +
    + ) + : null + } + { + screen === PASSWORD_PROMPT_SCREEN + ? ( +
    + {t('syncWithMobileDescNewUsers')} +
    + ) + : null + } +
    +
    + {this.renderContent()} +
    + {this.renderFooter()} +
    + ) + } +} diff --git a/ui/app/pages/mobile-sync/mobile-sync.container.js b/ui/app/pages/mobile-sync/mobile-sync.container.js new file mode 100644 index 000000000..915563841 --- /dev/null +++ b/ui/app/pages/mobile-sync/mobile-sync.container.js @@ -0,0 +1,25 @@ +import { connect } from 'react-redux' +import { displayWarning, requestRevealSeedWords, fetchInfoToSync } from '../../store/actions' +import MobileSyncPage from './mobile-sync.component' + +const mapDispatchToProps = (dispatch) => { + return { + requestRevealSeedWords: password => dispatch(requestRevealSeedWords(password)), + fetchInfoToSync: () => dispatch(fetchInfoToSync()), + displayWarning: (message) => dispatch(displayWarning(message || null)), + } +} + +const mapStateToProps = state => { + const { + metamask: { + selectedAddress, + }, + } = state + + return { + selectedAddress, + } +} + +module.exports = connect(mapStateToProps, mapDispatchToProps)(MobileSyncPage) diff --git a/ui/app/pages/permissions-connect/choose-account/choose-account.component.js b/ui/app/pages/permissions-connect/choose-account/choose-account.component.js new file mode 100644 index 000000000..1a3a8fc49 --- /dev/null +++ b/ui/app/pages/permissions-connect/choose-account/choose-account.component.js @@ -0,0 +1,108 @@ +import PropTypes from 'prop-types' +import React, { Component } from 'react' +import Identicon from '../../../components/ui/identicon' +import { PRIMARY } from '../../../helpers/constants/common' +import UserPreferencedCurrencyDisplay from '../../../components/app/user-preferenced-currency-display' + +export default class ChooseAccount extends Component { + static propTypes = { + accounts: PropTypes.arrayOf(PropTypes.shape({ + address: PropTypes.string, + addressLabel: PropTypes.string, + lastConnectedDate: PropTypes.string, + balance: PropTypes.string, + })).isRequired, + originName: PropTypes.string.isRequired, + selectAccount: PropTypes.func.isRequired, + selectNewAccountViaModal: PropTypes.func.isRequired, + nativeCurrency: PropTypes.string.isRequired, + addressLastConnectedMap: PropTypes.object, + cancelPermissionsRequest: PropTypes.func.isRequired, + permissionsRequestId: PropTypes.string.isRequired, + } + + static defaultProps = { + addressLastConnectedMap: {}, + } + + static contextTypes = { + t: PropTypes.func, + }; + + renderAccountsList = () => { + const { accounts, selectAccount, nativeCurrency, addressLastConnectedMap } = this.props + return ( +
    + { + accounts.map((account, index) => { + const { address, addressLabel, balance } = account + return ( +
    selectAccount(address) } + className="permissions-connect-choose-account__account" + > +
    + +
    +
    { addressLabel }
    + +
    +
    + { addressLastConnectedMap[address] + ? ( +
    + { this.context.t('lastConnected') } + { addressLastConnectedMap[address] } +
    + ) + : null + } +
    + ) + }) + } +
    + ) + } + + render () { + const { originName, selectNewAccountViaModal, permissionsRequestId, cancelPermissionsRequest } = this.props + const { t } = this.context + return ( +
    +
    + { t('chooseAnAcount') } +
    +
    + { t('toConnectWith', [originName]) } +
    + { this.renderAccountsList() } +
    +
    cancelPermissionsRequest(permissionsRequestId) } + className="permissions-connect-choose-account__cancel" + > + { t('cancel') } +
    +
    selectNewAccountViaModal() } + className="permissions-connect-choose-account__new-account" + > + { t('newAccount') } +
    +
    +
    + ) + } +} diff --git a/ui/app/pages/permissions-connect/choose-account/index.js b/ui/app/pages/permissions-connect/choose-account/index.js new file mode 100644 index 000000000..10b4cd224 --- /dev/null +++ b/ui/app/pages/permissions-connect/choose-account/index.js @@ -0,0 +1 @@ +export { default } from './choose-account.component' diff --git a/ui/app/pages/permissions-connect/choose-account/index.scss b/ui/app/pages/permissions-connect/choose-account/index.scss new file mode 100644 index 000000000..12c124b18 --- /dev/null +++ b/ui/app/pages/permissions-connect/choose-account/index.scss @@ -0,0 +1,97 @@ +.permissions-connect-choose-account { + display: flex; + flex-direction: column; + margin-top: 40px; + width: 100%; + align-items: center; + + &__title { + @extend %header--18; + } + + &__text { + @extend %content-text; + line-height: 25px; + } + + &__accounts-list { + width: 393px; + border: 1px solid #D0D5DA; + box-sizing: border-box; + border-radius: 8px; + margin-top: 36px; + + @media screen and (max-width: 575px) { + width: 100%; + } + } + + &__account-info-wrapper { + display: flex; + justify-content: flex-start; + } + + &__account { + display: flex; + align-items: center; + padding: 16px; + border-bottom: 1px solid #D2D8DD; + justify-content: space-between; + + &:last-of-type { + border-bottom: none; + } + + &:hover { + background: aliceblue; + cursor: pointer; + } + + &__info { + display: flex; + flex-direction: column; + margin-left: 16px; + } + + &__label { + @extend %header--18; + line-height: 25px; + color: #000000; + } + + &__balance { + @extend %content-text; + line-height: 17px; + color: #6A737D; + } + + &__last-connected { + @extend %content-text; + font-size: 10px; + line-height: 140.62%; + display: flex; + flex-direction: column; + align-items: flex-end; + color: #037DD6; + } + } + + &__new-account, &__cancel { + @extend %content-text; + line-height: 20px; + color: #037DD6; + margin-top: 24px; + cursor: pointer; + } + + &__cancel { + color: $Red-400; + } + + &__bottom-buttons { + display: flex; + justify-content: space-around; + width: 393px; + } + +} \ No newline at end of file diff --git a/ui/app/pages/permissions-connect/index.js b/ui/app/pages/permissions-connect/index.js new file mode 100644 index 000000000..06798a2f6 --- /dev/null +++ b/ui/app/pages/permissions-connect/index.js @@ -0,0 +1 @@ +export { default } from './permissions-connect.container' diff --git a/ui/app/pages/permissions-connect/index.scss b/ui/app/pages/permissions-connect/index.scss new file mode 100644 index 000000000..fe2f5182a --- /dev/null +++ b/ui/app/pages/permissions-connect/index.scss @@ -0,0 +1,11 @@ +@import 'permissions-connect-header/index'; + +@import 'permissions-connect-footer/index'; + +@import 'choose-account/index'; + +.permissions-connect { + width: 100%; + position: relative; + background: white; +} diff --git a/ui/app/pages/permissions-connect/permissions-connect-footer/index.js b/ui/app/pages/permissions-connect/permissions-connect-footer/index.js new file mode 100644 index 000000000..8096e1e3d --- /dev/null +++ b/ui/app/pages/permissions-connect/permissions-connect-footer/index.js @@ -0,0 +1 @@ +export { default } from './permissions-connect-footer.component' diff --git a/ui/app/pages/permissions-connect/permissions-connect-footer/index.scss b/ui/app/pages/permissions-connect/permissions-connect-footer/index.scss new file mode 100644 index 000000000..ff805e541 --- /dev/null +++ b/ui/app/pages/permissions-connect/permissions-connect-footer/index.scss @@ -0,0 +1,27 @@ +.permissions-connect-footer { + display: flex; + flex-direction: column; + width: 100%; + align-items: center; + position: absolute; + bottom: 50px; + + @media screen and (max-width: 575px) { + bottom: 5px; + } + + &__text { + @extend %content-text; + font-size: 12px; + line-height: 17px; + color: #6A737D; + display: flex; + margin-top: 10px; + + &--link { + color: #037DD6; + margin-left: 4px; + cursor: pointer; + } + } +} \ No newline at end of file diff --git a/ui/app/pages/permissions-connect/permissions-connect-footer/permissions-connect-footer.component.js b/ui/app/pages/permissions-connect/permissions-connect-footer/permissions-connect-footer.component.js new file mode 100644 index 000000000..b8fad97a3 --- /dev/null +++ b/ui/app/pages/permissions-connect/permissions-connect-footer/permissions-connect-footer.component.js @@ -0,0 +1,27 @@ +import PropTypes from 'prop-types' +import React, { Component } from 'react' + +export default class PermissionsConnectFooter extends Component { + static contextTypes = { + t: PropTypes.func, + } + + render () { + const { t } = this.context + return ( +
    + +
    +
    { t('onlyConnectTrust') }
    +
    { + global.platform.openWindow({ url: 'https://medium.com/metamask/privacy-mode-is-now-enabled-by-default-1c1c957f4d57' }) + }} + >{ t('learnAboutRisks') } +
    +
    +
    + ) + } +} diff --git a/ui/app/pages/permissions-connect/permissions-connect-header/index.js b/ui/app/pages/permissions-connect/permissions-connect-header/index.js new file mode 100644 index 000000000..e9de1a06a --- /dev/null +++ b/ui/app/pages/permissions-connect/permissions-connect-header/index.js @@ -0,0 +1 @@ +export { default } from './permissions-connect-header.component' diff --git a/ui/app/pages/permissions-connect/permissions-connect-header/index.scss b/ui/app/pages/permissions-connect/permissions-connect-header/index.scss new file mode 100644 index 000000000..ca239bf17 --- /dev/null +++ b/ui/app/pages/permissions-connect/permissions-connect-header/index.scss @@ -0,0 +1,15 @@ +.permissions-connect-header { + margin-top: 26px; + margin-left: 20px; + display: flex; + justify-content: space-between; + align-items: flex-end; + + &__page-count { + @extend %content-text; + margin-right: 30px; + font-size: 12px; + line-height: 17px; + color: #6A737D; + } +} \ No newline at end of file diff --git a/ui/app/pages/permissions-connect/permissions-connect-header/permissions-connect-header.component.js b/ui/app/pages/permissions-connect/permissions-connect-header/permissions-connect-header.component.js new file mode 100644 index 000000000..512866c38 --- /dev/null +++ b/ui/app/pages/permissions-connect/permissions-connect-header/permissions-connect-header.component.js @@ -0,0 +1,25 @@ +import PropTypes from 'prop-types' +import React, { Component } from 'react' +import MetaFoxLogo from '../../../components/ui/metafox-logo' +import { DEFAULT_ROUTE } from '../../../helpers/constants/routes' + +export default class PermissionsConnectHeader extends Component { + static propTypes = { + page: PropTypes.number.isRequired, + } + + render () { + const { page } = this.props + return ( +
    + history.push(DEFAULT_ROUTE)} + /> +
    + { `${page}/2` } +
    +
    + ) + } +} diff --git a/ui/app/pages/permissions-connect/permissions-connect.component.js b/ui/app/pages/permissions-connect/permissions-connect.component.js new file mode 100644 index 000000000..69dcbd390 --- /dev/null +++ b/ui/app/pages/permissions-connect/permissions-connect.component.js @@ -0,0 +1,208 @@ +import PropTypes from 'prop-types' +import React, { Component } from 'react' +import PermissionsConnectHeader from './permissions-connect-header' +import PermissionsConnectFooter from './permissions-connect-footer' +import ChooseAccount from './choose-account' +import { getEnvironmentType } from '../../../../app/scripts/lib/util' +import { + ENVIRONMENT_TYPE_FULLSCREEN, + ENVIRONMENT_TYPE_NOTIFICATION, + ENVIRONMENT_TYPE_POPUP, +} from '../../../../app/scripts/lib/enums' +import { DEFAULT_ROUTE, CONNECTED_ROUTE } from '../../helpers/constants/routes' +import PermissionPageContainer from '../../components/app/permission-page-container' + +export default class PermissionConnect extends Component { + static propTypes = { + approvePermissionsRequest: PropTypes.func.isRequired, + rejectPermissionsRequest: PropTypes.func.isRequired, + getRequestAccountTabIds: PropTypes.func.isRequired, + getCurrentWindowTab: PropTypes.func.isRequired, + accounts: PropTypes.array.isRequired, + originName: PropTypes.string, + showNewAccountModal: PropTypes.func.isRequired, + newAccountNumber: PropTypes.number.isRequired, + nativeCurrency: PropTypes.string, + permissionsRequest: PropTypes.object, + addressLastConnectedMap: PropTypes.object, + requestAccountTabs: PropTypes.object, + permissionsRequestId: PropTypes.string, + domains: PropTypes.object, + history: PropTypes.object.isRequired, + } + + static defaultProps = { + originName: '', + nativeCurrency: '', + permissionsRequest: undefined, + addressLastConnectedMap: {}, + requestAccountTabs: {}, + permissionsRequestId: '', + domains: {}, + } + + static contextTypes = { + t: PropTypes.func, + } + + state = { + page: 1, + selectedAccountAddress: '', + permissionAccepted: null, + originName: this.props.originName, + } + + beforeUnload = () => { + const { permissionsRequestId, rejectPermissionsRequest } = this.props + const { permissionAccepted } = this.state + + if (permissionAccepted === null && permissionsRequestId) { + rejectPermissionsRequest(permissionsRequestId) + } + } + + removeBeforeUnload = () => { + if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_FULLSCREEN) { + window.removeEventListener('beforeunload', this.beforeUnload) + } + } + + componentDidUpdate (prevProps) { + const { domains, permissionsRequestId } = this.props + const { originName, page } = this.state + + if (!permissionsRequestId && prevProps.permissionsRequestId && page !== null) { + const permissionDataForDomain = domains && domains[originName] || {} + const permissionsForDomain = permissionDataForDomain.permissions || [] + const prevPermissionDataForDomain = prevProps.domains && prevProps.domains[originName] || {} + const prevPermissionsForDomain = prevPermissionDataForDomain.permissions || [] + const addedAPermission = permissionsForDomain.length > prevPermissionsForDomain.length + if (addedAPermission) { + this.redirectFlow(true) + } else { + this.redirectFlow(false) + } + } else if (permissionsRequestId && prevProps.permissionsRequestId && + permissionsRequestId !== prevProps.permissionsRequestId && page !== null) { + this.setState({ + originName: this.props.originName, + page: 1, + }) + } + } + + selectAccount = (address) => { + this.setState({ + page: 2, + selectedAccountAddress: address, + }) + } + + redirectFlow (accepted) { + const { requestAccountTabs, history } = this.props + const { originName } = this.state + + this.setState({ + page: null, + permissionAccepted: accepted, + }) + this.removeBeforeUnload() + + if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_FULLSCREEN) { + setTimeout(async () => { + const currentTab = await global.platform.currentTab() + try { + if (currentTab.active) { + await global.platform.switchToTab(requestAccountTabs[originName]) + } + } finally { + global.platform.closeTab(currentTab.id) + } + }, 2000) + } else if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_NOTIFICATION) { + history.push(DEFAULT_ROUTE) + } else if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) { + history.push(CONNECTED_ROUTE) + } + } + + componentDidMount () { + const { + getCurrentWindowTab, + getRequestAccountTabIds, + } = this.props + getCurrentWindowTab() + getRequestAccountTabIds() + + if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_FULLSCREEN) { + window.addEventListener('beforeunload', this.beforeUnload) + } + } + + render () { + const { + approvePermissionsRequest, + rejectPermissionsRequest, + accounts, + showNewAccountModal, + newAccountNumber, + nativeCurrency, + permissionsRequest, + addressLastConnectedMap, + permissionsRequestId, + } = this.props + const { page, selectedAccountAddress, permissionAccepted, originName } = this.state + + return ( +
    + { page !== null + ? + : null + } + { page === 1 + ? ( + this.selectAccount(address)} + selectNewAccountViaModal={() => { + showNewAccountModal({ + onCreateNewAccount: this.selectAccount, + newAccountNumber, + }) + }} + addressLastConnectedMap={addressLastConnectedMap} + cancelPermissionsRequest={requestId => { + if (requestId) { + rejectPermissionsRequest(requestId) + this.redirectFlow(false) + } + }} + permissionsRequestId={permissionsRequestId} + /> + ) + : ( +
    + { + approvePermissionsRequest(requestId, accounts) + this.redirectFlow(true) + }} + rejectPermissionsRequest={requestId => { + rejectPermissionsRequest(requestId) + this.redirectFlow(false) + }} + selectedIdentity={accounts.find(account => account.address === selectedAccountAddress)} + redirect={page === null} + permissionRejected={ permissionAccepted === false } + /> + +
    + ) + } +
    + ) + } +} diff --git a/ui/app/pages/permissions-connect/permissions-connect.container.js b/ui/app/pages/permissions-connect/permissions-connect.container.js new file mode 100644 index 000000000..1209bd9cc --- /dev/null +++ b/ui/app/pages/permissions-connect/permissions-connect.container.js @@ -0,0 +1,66 @@ +import { connect } from 'react-redux' +import { compose } from 'recompose' +import { withRouter } from 'react-router-dom' +import PermissionApproval from './permissions-connect.component' +import { + getFirstPermissionRequest, + getNativeCurrency, + getAccountsWithLabels, + getLastConnectedInfo, + getPermissionsDomains, +} from '../../selectors/selectors' +import { formatDate } from '../../helpers/utils/util' +import { approvePermissionsRequest, rejectPermissionsRequest, showModal, getCurrentWindowTab, getRequestAccountTabIds } from '../../store/actions' + +const mapStateToProps = state => { + const permissionsRequest = getFirstPermissionRequest(state) + const { metadata = {} } = permissionsRequest || {} + const { origin } = metadata + const nativeCurrency = getNativeCurrency(state) + + const accountsWithLabels = getAccountsWithLabels(state) + + const { requestAccountTabs = {} } = state.appState + + const lastConnectedInfo = getLastConnectedInfo(state) || {} + const addressLastConnectedMap = lastConnectedInfo[origin] || {} + + Object.keys(addressLastConnectedMap).forEach(key => { + addressLastConnectedMap[key] = formatDate(addressLastConnectedMap[key], 'yyyy-M-d') + }) + + const permissionsRequestId = (permissionsRequest && permissionsRequest.metadata) ? permissionsRequest.metadata.id : null + + return { + permissionsRequest, + permissionsRequestId, + accounts: accountsWithLabels, + originName: origin, + newAccountNumber: accountsWithLabels.length + 1, + nativeCurrency, + requestAccountTabs, + addressLastConnectedMap, + domains: getPermissionsDomains(state), + } +} + +const mapDispatchToProps = dispatch => { + return { + approvePermissionsRequest: (requestId, accounts) => dispatch(approvePermissionsRequest(requestId, accounts)), + rejectPermissionsRequest: requestId => dispatch(rejectPermissionsRequest(requestId)), + showNewAccountModal: ({ onCreateNewAccount, newAccountNumber }) => { + return dispatch(showModal({ + name: 'NEW_ACCOUNT', + onCreateNewAccount, + newAccountNumber, + })) + }, + getRequestAccountTabIds: () => dispatch(getRequestAccountTabIds()), + getCurrentWindowTab: () => dispatch(getCurrentWindowTab()), + } +} + +export default compose( + withRouter, + connect(mapStateToProps, mapDispatchToProps) +)(PermissionApproval) diff --git a/ui/app/pages/provider-approval/index.js b/ui/app/pages/provider-approval/index.js deleted file mode 100644 index 4162f3155..000000000 --- a/ui/app/pages/provider-approval/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './provider-approval.container' diff --git a/ui/app/pages/provider-approval/provider-approval.component.js b/ui/app/pages/provider-approval/provider-approval.component.js deleted file mode 100644 index 8532fe60d..000000000 --- a/ui/app/pages/provider-approval/provider-approval.component.js +++ /dev/null @@ -1,36 +0,0 @@ -import PropTypes from 'prop-types' -import React, { Component } from 'react' -import ProviderPageContainer from '../../components/app/provider-page-container' - -export default class ProviderApproval extends Component { - static propTypes = { - approveProviderRequestByOrigin: PropTypes.func.isRequired, - rejectProviderRequestByOrigin: PropTypes.func.isRequired, - providerRequest: PropTypes.exact({ - hostname: PropTypes.string.isRequired, - siteImage: PropTypes.string, - siteTitle: PropTypes.string, - origin: PropTypes.string.isRequired, - extensionId: PropTypes.string, - }).isRequired, - }; - - static contextTypes = { - t: PropTypes.func, - }; - - render () { - const { approveProviderRequestByOrigin, providerRequest, rejectProviderRequestByOrigin } = this.props - return ( - - ) - } -} diff --git a/ui/app/pages/provider-approval/provider-approval.container.js b/ui/app/pages/provider-approval/provider-approval.container.js deleted file mode 100644 index 1e167ddb7..000000000 --- a/ui/app/pages/provider-approval/provider-approval.container.js +++ /dev/null @@ -1,12 +0,0 @@ -import { connect } from 'react-redux' -import ProviderApproval from './provider-approval.component' -import { approveProviderRequestByOrigin, rejectProviderRequestByOrigin } from '../../store/actions' - -function mapDispatchToProps (dispatch) { - return { - approveProviderRequestByOrigin: origin => dispatch(approveProviderRequestByOrigin(origin)), - rejectProviderRequestByOrigin: origin => dispatch(rejectProviderRequestByOrigin(origin)), - } -} - -export default connect(null, mapDispatchToProps)(ProviderApproval) diff --git a/ui/app/pages/routes/index.js b/ui/app/pages/routes/index.js index 01e61b1b4..b2a2ced68 100644 --- a/ui/app/pages/routes/index.js +++ b/ui/app/pages/routes/index.js @@ -6,7 +6,12 @@ import { compose } from 'recompose' import actions from '../../store/actions' import log from 'loglevel' import IdleTimer from 'react-idle-timer' -import {getNetworkIdentifier, preferencesSelector} from '../../selectors/selectors' +import { + getNetworkIdentifier, + preferencesSelector, + hasPermissionRequests, + getAddressConnectedToCurrentTab, +} from '../../selectors/selectors' import classnames from 'classnames' // init @@ -25,9 +30,11 @@ import Settings from '../settings' import Authenticated from '../../helpers/higher-order-components/authenticated' import Initialized from '../../helpers/higher-order-components/initialized' import Lock from '../lock' +import PermissionsConnect from '../permissions-connect' +import ConnectedSites from '../connected-sites' const RestoreVaultPage = require('../keychains/restore-vault').default const RevealSeedConfirmation = require('../keychains/reveal-seed') -const MobileSyncPage = require('../mobile-sync') +const MobileSyncPage = require('../mobile-sync').default const AddTokenPage = require('../add-token') const ConfirmAddTokenPage = require('../confirm-add-token') const ConfirmAddSuggestedTokenPage = require('../confirm-add-suggested-token') @@ -67,6 +74,8 @@ import { CONFIRM_TRANSACTION_ROUTE, INITIALIZE_ROUTE, INITIALIZE_UNLOCK_ROUTE, + CONNECT_ROUTE, + CONNECTED_ROUTE, } from '../../helpers/constants/routes' // enums @@ -98,6 +107,20 @@ class Routes extends Component { }) } + componentDidMount () { + const { addressConnectedToCurrentTab, showAccountDetail, selectedAddress } = this.props + if (addressConnectedToCurrentTab && addressConnectedToCurrentTab !== selectedAddress) { + showAccountDetail(addressConnectedToCurrentTab) + } + } + + componentDidUpdate (prevProps) { + const { addressConnectedToCurrentTab, showAccountDetail } = this.props + if (addressConnectedToCurrentTab && addressConnectedToCurrentTab !== prevProps.addressConnectedToCurrentTab) { + showAccountDetail(addressConnectedToCurrentTab) + } + } + renderRoutes () { const { autoLogoutTimeLimit, setLastActiveTime } = this.props @@ -116,6 +139,8 @@ class Routes extends Component { + + ) @@ -141,13 +166,8 @@ class Routes extends Component { return Boolean(matchPath(location.pathname, { path: CONFIRM_TRANSACTION_ROUTE, exact: false })) } - hasProviderRequests () { - const { providerRequests } = this.props - return Array.isArray(providerRequests) && providerRequests.length > 0 - } - hideAppHeader () { - const { location } = this.props + const { location, hasPermissionsRequests } = this.props const isInitializing = Boolean(matchPath(location.pathname, { path: INITIALIZE_ROUTE, exact: false, @@ -162,7 +182,15 @@ class Routes extends Component { } if (window.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_POPUP) { - return this.onConfirmPage() || this.hasProviderRequests() + return this.onConfirmPage() || hasPermissionsRequests + } + + const isHandlingPermissionsRequest = Boolean(matchPath(location.pathname, { + path: CONNECT_ROUTE, exact: false, + })) + + if (hasPermissionsRequests || isHandlingPermissionsRequest) { + return true } } @@ -260,8 +288,10 @@ class Routes extends Component { toggleMetamaskActive () { if (!this.props.isUnlocked) { // currently inactive: redirect to password box - var passwordBox = document.querySelector('input[type=password]') - if (!passwordBox) return + const passwordBox = document.querySelector('input[type=password]') + if (!passwordBox) { + return + } passwordBox.focus() } else { // currently active: deactivate @@ -332,6 +362,7 @@ Routes.propTypes = { textDirection: PropTypes.string, network: PropTypes.string, provider: PropTypes.object, + selectedAddress: PropTypes.string, frequentRpcListDetail: PropTypes.array, currentView: PropTypes.object, sidebar: PropTypes.object, @@ -346,12 +377,18 @@ Routes.propTypes = { isMouseUser: PropTypes.bool, setMouseUserState: PropTypes.func, providerId: PropTypes.string, - providerRequests: PropTypes.array, + hasPermissionsRequests: PropTypes.bool, autoLogoutTimeLimit: PropTypes.number, + addressConnectedToCurrentTab: PropTypes.string, + showAccountDetail: PropTypes.func, +} + +Routes.defaultProps = { + selectedAddress: undefined, } function mapStateToProps (state) { - const { appState, metamask } = state + const { appState } = state const { sidebar, alertOpen, @@ -375,12 +412,14 @@ function mapStateToProps (state) { submittedPendingTransactions: submittedPendingTransactionsSelector(state), network: state.metamask.network, provider: state.metamask.provider, + selectedAddress: state.metamask.selectedAddress, frequentRpcListDetail: state.metamask.frequentRpcListDetail || [], currentCurrency: state.metamask.currentCurrency, isMouseUser: state.appState.isMouseUser, providerId: getNetworkIdentifier(state), autoLogoutTimeLimit, - providerRequests: metamask.providerRequests, + hasPermissionsRequests: hasPermissionRequests(state), + addressConnectedToCurrentTab: getAddressConnectedToCurrentTab(state), } } @@ -391,6 +430,7 @@ function mapDispatchToProps (dispatch) { setCurrentCurrencyToUSD: () => dispatch(actions.setCurrentCurrency('usd')), setMouseUserState: (isMouseUser) => dispatch(actions.setMouseUserState(isMouseUser)), setLastActiveTime: () => dispatch(actions.setLastActiveTime()), + showAccountDetail: address => dispatch(actions.showAccountDetail(address)), } } diff --git a/ui/app/pages/send/account-list-item/account-list-item.component.js b/ui/app/pages/send/account-list-item/account-list-item.component.js index 20ced2f09..8c676f419 100644 --- a/ui/app/pages/send/account-list-item/account-list-item.component.js +++ b/ui/app/pages/send/account-list-item/account-list-item.component.js @@ -44,30 +44,32 @@ export default class AccountListItem extends Component { const { name, address, balance } = account || {} - return (
    handleClick && handleClick({ name, address, balance })} - > + return ( +
    handleClick && handleClick({ name, address, balance })} + > -
    - +
    + -
    { name || address }
    +
    { name || address }
    - {icon &&
    { icon }
    } + {icon &&
    { icon }
    } -
    +
    - {displayAddress && name &&
    - { checksumAddress(address) } -
    } + {displayAddress && name && ( +
    + { checksumAddress(address) } +
    + )} - { - displayBalance && ( + {displayBalance && ( -
    +
    { - balanceIsCached ? * : null + balanceIsCached + ? * + : null }
    - { - showFiat && ( - - ) - } + {showFiat && ( + + )}
    - ) - } + )} -
    ) +
    + ) } } diff --git a/ui/app/pages/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 5a2bd5638..e54a8c493 100644 --- a/ui/app/pages/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 @@ -23,17 +23,19 @@ describe('AccountListItem Component', function () { let wrapper beforeEach(() => { - wrapper = shallow(} - />, { context: { t: str => str + '_t' } }) + wrapper = shallow(( + } + /> + ), { context: { t: str => str + '_t' } }) }) afterEach(() => { diff --git a/ui/app/pages/send/send-content/add-recipient/add-recipient.component.js b/ui/app/pages/send/send-content/add-recipient/add-recipient.component.js index e5edbc08d..a2e6b49db 100644 --- a/ui/app/pages/send/send-content/add-recipient/add-recipient.component.js +++ b/ui/app/pages/send/send-content/add-recipient/add-recipient.component.js @@ -64,7 +64,6 @@ export default class AddRecipient extends Component { state = { isShowingTransfer: false, - isShowingAllRecent: false, } selectRecipient = (to, nickname = '') => { @@ -156,7 +155,7 @@ export default class AddRecipient extends Component { className="send__select-recipient-wrapper__list__link" onClick={() => this.setState({ isShowingTransfer: false })} > -
    +
    { t('backToAll') }
    { - if (address === ZERO_ADDRESS) throw new Error(this.context.t('noAddressForName')) - if (address === ZERO_X_ERROR_ADDRESS) throw new Error(this.context.t('ensRegistrationError')) + if (address === ZERO_ADDRESS) { + throw new Error(this.context.t('noAddressForName')) + } + if (address === ZERO_X_ERROR_ADDRESS) { + throw new Error(this.context.t('ensRegistrationError')) + } this.props.updateEnsResolution(address) }) .catch((reason) => { @@ -230,9 +233,11 @@ export default class EnsInput extends Component { } ensIconContents () { - const { loadingEns, ensFailure, ensResolution, toError } = this.state || { ensResolution: ZERO_ADDRESS } + const { loadingEns, ensFailure, ensResolution, toError } = this.state - if (toError) return + if (toError) { + return + } if (loadingEns) { return ( diff --git a/ui/app/pages/send/send-content/add-recipient/tests/add-recipient-component.test.js b/ui/app/pages/send/send-content/add-recipient/tests/add-recipient-component.test.js index 1271c9810..115ad5b62 100644 --- a/ui/app/pages/send/send-content/add-recipient/tests/add-recipient-component.test.js +++ b/ui/app/pages/send/send-content/add-recipient/tests/add-recipient-component.test.js @@ -19,23 +19,25 @@ describe('AddRecipient Component', function () { let instance beforeEach(() => { - wrapper = shallow(, { context: { t: str => str + '_t' } }) + wrapper = shallow(( + + ), { context: { t: str => str + '_t' } }) instance = wrapper.instance() }) diff --git a/ui/app/pages/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 30ef4c678..48bc32d3a 100644 --- a/ui/app/pages/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 @@ -18,15 +18,17 @@ describe('AmountMaxButton Component', function () { let instance beforeEach(() => { - wrapper = shallow(, { + wrapper = shallow(( + + ), { context: { t: str => str + '_t', metricsEvent: () => {}, diff --git a/ui/app/pages/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 41a36e992..41474acf2 100644 --- a/ui/app/pages/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 @@ -27,23 +27,25 @@ describe('SendAmountRow Component', function () { let instance beforeEach(() => { - wrapper = shallow(, { context: { t: str => str + '_t' } }) + wrapper = shallow(( + + ), { context: { t: str => str + '_t' } }) instance = wrapper.instance() }) 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 index 1dcd0bd2c..c0b604505 100644 --- 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 @@ -128,7 +128,8 @@ export default class SendAssetRow extends Component { return (
    this.selectToken(address)} >
    diff --git a/ui/app/pages/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 5f5d26231..42b19397b 100644 --- a/ui/app/pages/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 @@ -17,7 +17,7 @@ export default class SendDropdownList extends Component { getListItemIcon (accountAddress, activeAddress) { return accountAddress === activeAddress - ? + ? : null } @@ -29,24 +29,28 @@ export default class SendDropdownList extends Component { activeAddress, } = this.props - return (
    -
    closeDropdown()} - /> -
    - {accounts.map((account, index) => { - onSelect(account) - closeDropdown() - }} - icon={this.getListItemIcon(account.address, activeAddress)} - key={`send-dropdown-account-#${index}`} - />)} + return ( +
    +
    closeDropdown()} + /> +
    + {accounts.map((account, index) => ( + { + onSelect(account) + closeDropdown() + }} + icon={this.getListItemIcon(account.address, activeAddress)} + key={`send-dropdown-account-#${index}`} + /> + ))} +
    -
    ) + ) } } diff --git a/ui/app/pages/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 bc6c97586..77efddfd4 100644 --- a/ui/app/pages/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 @@ -17,16 +17,18 @@ describe('SendDropdownList Component', function () { let wrapper beforeEach(() => { - wrapper = shallow(, { context: { t: str => str + '_t' } }) + wrapper = shallow(( + + ), { context: { t: str => str + '_t' } }) }) afterEach(() => { @@ -39,7 +41,7 @@ describe('SendDropdownList Component', function () { it('should return check icon if the passed addresses are the same', () => { assert.deepEqual( wrapper.instance().getListItemIcon('mockAccount0', 'mockAccount0'), - + ) }) diff --git a/ui/app/pages/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 37af59e29..473dbb0b1 100644 --- a/ui/app/pages/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 @@ -38,12 +38,16 @@ export default class GasFeeDisplay extends Component {
    ) : gasLoadingError - ?
    - {this.context.t('setGasPrice')} -
    - :
    - {this.context.t('loading')} -
    + ? ( +
    + {this.context.t('setGasPrice')} +
    + ) + : ( +
    + {this.context.t('loading')} +
    + ) } -
    -
    -
    - ) - } - - renderApprovedOriginsList () { - const { t } = this.context - const { approvedOrigins, rejectProviderRequestByOrigin, showClearApprovalModal } = this.props - const approvedEntries = Object.entries(approvedOrigins) - const approvalListEmpty = approvedEntries.length === 0 - - return ( -
    -
    - { t('connected') } - - { t('connectedDescription') } - -
    -
    - { - approvalListEmpty - ?
    - : null - } - { - approvedEntries.map(([origin, { siteTitle, siteImage }]) => ( - { - rejectProviderRequestByOrigin(origin) - }} - /> - )) - } -
    -
    - -
    -
    - ) - } - - render () { - return ( -
    - { this.renderNewOriginInput() } - { this.renderApprovedOriginsList() } -
    - ) - } -} diff --git a/ui/app/pages/settings/connections-tab/connections-tab.container.js b/ui/app/pages/settings/connections-tab/connections-tab.container.js deleted file mode 100644 index cf3efc2b4..000000000 --- a/ui/app/pages/settings/connections-tab/connections-tab.container.js +++ /dev/null @@ -1,39 +0,0 @@ -import ConnectionsTab from './connections-tab.component' -import { compose } from 'recompose' -import { connect } from 'react-redux' -import { withRouter } from 'react-router-dom' -import { - approveProviderRequestByOrigin, - rejectProviderRequestByOrigin, - showModal, -} from '../../../store/actions' - -export const mapStateToProps = state => { - const { - activeTab, - metamask, - } = state - const { - approvedOrigins, - } = metamask - - return { - activeTab, - approvedOrigins, - } -} - -export const mapDispatchToProps = dispatch => { - return { - approveProviderRequestByOrigin: (origin) => dispatch(approveProviderRequestByOrigin(origin)), - rejectProviderRequestByOrigin: (origin) => dispatch(rejectProviderRequestByOrigin(origin)), - showClearApprovalModal: () => dispatch(showModal({ - name: 'CLEAR_APPROVED_ORIGINS', - })), - } -} - -export default compose( - withRouter, - connect(mapStateToProps, mapDispatchToProps) -)(ConnectionsTab) diff --git a/ui/app/pages/settings/connections-tab/index.js b/ui/app/pages/settings/connections-tab/index.js deleted file mode 100644 index b04f4e33a..000000000 --- a/ui/app/pages/settings/connections-tab/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './connections-tab.container' diff --git a/ui/app/pages/settings/connections-tab/index.scss b/ui/app/pages/settings/connections-tab/index.scss deleted file mode 100644 index 249a7193f..000000000 --- a/ui/app/pages/settings/connections-tab/index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './connected-site-row/index'; diff --git a/ui/app/pages/settings/contact-list-tab/add-contact/add-contact.component.js b/ui/app/pages/settings/contact-list-tab/add-contact/add-contact.component.js index 878ea10c2..f3cd7227a 100644 --- a/ui/app/pages/settings/contact-list-tab/add-contact/add-contact.component.js +++ b/ui/app/pages/settings/contact-list-tab/add-contact/add-contact.component.js @@ -23,7 +23,7 @@ export default class AddContact extends PureComponent { } state = { - nickname: '', + newName: '', ethAddress: '', ensAddress: '', error: '', @@ -63,7 +63,9 @@ export default class AddContact extends PureComponent { return ( { this.props.scanQrCode() }} + scanQrCode={_ => { + this.props.scanQrCode() + }} onChange={this.dValidate} onPaste={text => this.setState({ ethAddress: text })} onReset={() => this.setState({ ethAddress: '', ensAddress: '' })} @@ -83,12 +85,14 @@ export default class AddContact extends PureComponent { return (
    - {this.state.ensAddress &&
    - -
    - { this.state.ensAddress } + {this.state.ensAddress && ( +
    + +
    + { this.state.ensAddress } +
    -
    } + )}
    diff --git a/ui/app/pages/settings/contact-list-tab/contact-list-tab.component.js b/ui/app/pages/settings/contact-list-tab/contact-list-tab.component.js index f7a01d672..835963a83 100644 --- a/ui/app/pages/settings/contact-list-tab/contact-list-tab.component.js +++ b/ui/app/pages/settings/contact-list-tab/contact-list-tab.component.js @@ -49,16 +49,19 @@ export default class ContactListTab extends Component { renderAddButton () { const { history } = this.props - return
    { - history.push(CONTACT_ADD_ROUTE) - }}> - -
    + return ( +
    { + history.push(CONTACT_ADD_ROUTE) + }} + > + +
    + ) } renderMyAccountsButton () { @@ -98,19 +101,23 @@ export default class ContactListTab extends Component { ContactContentComponent = AddContact } - return (ContactContentComponent &&
    - -
    ) + return (ContactContentComponent && ( +
    + +
    + )) } renderAddressBookContent () { const { hideAddressBook, showingMyAccounts } = this.props if (!hideAddressBook && !showingMyAccounts) { - return (
    - { this.renderMyAccountsButton() } - { this.renderAddresses() } -
    ) + return ( +
    + { this.renderMyAccountsButton() } + { this.renderAddresses() } +
    + ) } else if (!hideAddressBook && showingMyAccounts) { return () } @@ -123,9 +130,11 @@ export default class ContactListTab extends Component {
    { this.renderAddressBookContent() } { this.renderContactContent() } - {!addingContact &&
    - { this.renderAddButton() } -
    } + {!addingContact && ( +
    + { this.renderAddButton() } +
    + )}
    ) } diff --git a/ui/app/pages/settings/contact-list-tab/edit-contact/edit-contact.component.js b/ui/app/pages/settings/contact-list-tab/edit-contact/edit-contact.component.js index 9373cbe6b..88b14f5e3 100644 --- a/ui/app/pages/settings/contact-list-tab/edit-contact/edit-contact.component.js +++ b/ui/app/pages/settings/contact-list-tab/edit-contact/edit-contact.component.js @@ -45,7 +45,7 @@ export default class EditContact extends PureComponent { return (
    - + -
    + ? ( +
    + +
    + ) : null }
    diff --git a/ui/app/pages/settings/settings.component.js b/ui/app/pages/settings/settings.component.js index 975ab4e35..12d395f31 100644 --- a/ui/app/pages/settings/settings.component.js +++ b/ui/app/pages/settings/settings.component.js @@ -4,7 +4,6 @@ import { Switch, Route, matchPath, withRouter } from 'react-router-dom' import TabBar from '../../components/app/tab-bar' import c from 'classnames' import SettingsTab from './settings-tab' -import ConnectionsTab from './connections-tab' import NetworksTab from './networks-tab' import AdvancedTab from './advanced-tab' import InfoTab from './info-tab' @@ -15,7 +14,6 @@ import { ADVANCED_ROUTE, SECURITY_ROUTE, GENERAL_ROUTE, - CONNECTIONS_ROUTE, ABOUT_US_ROUTE, SETTINGS_ROUTE, NETWORKS_ROUTE, @@ -135,13 +133,19 @@ class SettingsPage extends PureComponent {
    initialBreadCrumbRoute && history.push(initialBreadCrumbRoute)} - >{subheaderText}
    - {breadCrumbTextKey &&
    {' > '}{t(breadCrumbTextKey)}
    } - {isAddressEntryPage &&
    {' > '}{addressName}
    } + > + {subheaderText} +
    + {breadCrumbTextKey && ( +
    + {' > '}{t(breadCrumbTextKey)} +
    + )} + {isAddressEntryPage && ( +
    + {' > '}{addressName} +
    + )}
    ) } @@ -154,7 +158,6 @@ class SettingsPage extends PureComponent { - { + const { address, name, balance } = account + return { + address, + truncatedAddress: `${address.slice(0, 6)}...${address.slice(-4)}`, + addressLabel: `${name} (...${address.slice(address.length - 4)})`, + label: name, + balance, + } + }) + return accountsWithLabels +} + function getCurrentAccountWithSendEtherInfo (state) { const currentAddress = getSelectedAddress(state) const accounts = accountsWithSendEtherInfoSelector(state) @@ -346,6 +378,22 @@ function getCustomNonceValue (state) { return String(state.metamask.customNonceValue) } +function getPermissionsDescriptions (state) { + return state.metamask.permissionsDescriptions +} + +function getPermissionsRequests (state) { + return state.metamask.permissionsRequests +} + +function getDomainMetadata (state) { + return state.metamask.domainMetadata +} + +function getActiveTab (state) { + return state.activeTab +} + function getMetaMetricState (state) { return { network: getCurrentNetworkId(state), @@ -379,3 +427,144 @@ function getKnownMethodData (state, data) { function getFeatureFlags (state) { return state.metamask.featureFlags } + +function getFirstPermissionRequest (state) { + const requests = getPermissionsRequests(state) + return requests && requests[0] ? requests[0] : null +} + +function hasPermissionRequests (state) { + return Boolean(getFirstPermissionRequest(state)) +} + +function getPermissionsDomains (state) { + return state.metamask.domains +} + +function getAddressConnectedDomainMap (state) { + const { + domains, + domainMetadata, + } = state.metamask + + const addressConnectedIconMap = {} + + if (domains) { + Object.keys(domains).forEach(domainKey => { + const { permissions } = domains[domainKey] + const { icon, name } = domainMetadata[domainKey] || {} + permissions.forEach(perm => { + const caveats = perm.caveats || [] + const exposedAccountCaveat = caveats.find(caveat => caveat.name === 'exposedAccounts') + if (exposedAccountCaveat && exposedAccountCaveat.value && exposedAccountCaveat.value.length) { + exposedAccountCaveat.value.forEach(address => { + const nameToRender = name || domainKey + addressConnectedIconMap[address] = addressConnectedIconMap[address] + ? { ...addressConnectedIconMap[address], [domainKey]: { icon, name: nameToRender } } + : { [domainKey]: { icon, name: nameToRender } } + }) + } + }) + }) + } + + return addressConnectedIconMap +} + +function getDomainToConnectedAddressMap (state) { + const { domains = {} } = state.metamask + + const domainToConnectedAddressMap = mapObjectValues(domains, (_, { permissions }) => { + const ethAccountsPermissions = permissions.filter(permission => permission.parentCapability === 'eth_accounts') + const ethAccountsPermissionsExposedAccountAddresses = ethAccountsPermissions.map(permission => { + const caveats = permission.caveats + const exposedAccountsCaveats = caveats.filter(caveat => caveat.name === 'exposedAccounts') + const exposedAccountsAddresses = exposedAccountsCaveats.map(caveat => caveat.value[0]) + return exposedAccountsAddresses + }) + const allAddressesConnectedToDomain = ethAccountsPermissionsExposedAccountAddresses.reduce((acc, arrayOfAddresses) => { + return [ ...acc, ...arrayOfAddresses ] + }, []) + return allAddressesConnectedToDomain + }) + + return domainToConnectedAddressMap +} + +function getAddressConnectedToCurrentTab (state) { + const domainToConnectedAddressMap = getDomainToConnectedAddressMap(state) + const originOfCurrentTab = getOriginOfCurrentTab(state) + const addressesConnectedToCurrentTab = domainToConnectedAddressMap[originOfCurrentTab] + const addressConnectedToCurrentTab = addressesConnectedToCurrentTab && addressesConnectedToCurrentTab[0] + return addressConnectedToCurrentTab +} + +function getRenderablePermissionsDomains (state) { + const { + domains = {}, + domainMetadata, + permissionsHistory, + permissionsDescriptions, + selectedAddress, + } = state.metamask + + const renderableDomains = Object.keys(domains).reduce((acc, domainKey) => { + const { permissions } = domains[domainKey] + const permissionsWithCaveatsForSelectedAddress = permissions.filter(perm => { + const caveats = perm.caveats || [] + const exposedAccountCaveat = caveats.find(caveat => caveat.name === 'exposedAccounts') + const exposedAccountCaveatValue = exposedAccountCaveat && exposedAccountCaveat.value && exposedAccountCaveat.value.length + ? exposedAccountCaveat.value[0] + : {} + return exposedAccountCaveatValue === selectedAddress + }) + + if (permissionsWithCaveatsForSelectedAddress.length) { + const permissionKeys = permissions.map(permission => permission.parentCapability) + const { + name, + icon, + extensionId, + } = domainMetadata[domainKey] || {} + const permissionsHistoryForDomain = permissionsHistory[domainKey] || {} + const ethAccountsPermissionsForDomain = permissionsHistoryForDomain['eth_accounts'] || {} + const accountsLastConnectedTime = ethAccountsPermissionsForDomain.accounts || {} + const selectedAddressLastConnectedTime = accountsLastConnectedTime[selectedAddress] + + const lastConnectedTime = selectedAddressLastConnectedTime + ? formatDate(selectedAddressLastConnectedTime, 'yyyy-M-d') + : '' + + return [ ...acc, { + name: name || domainKey, + secondaryName: name ? domainKey : '', + icon, + key: domainKey, + lastConnectedTime, + permissionDescriptions: permissionKeys.map(permissionKey => permissionsDescriptions[permissionKey]), + extensionId, + }] + } else { + return acc + } + }, []) + + return renderableDomains +} + +function getOriginOfCurrentTab (state) { + const { activeTab } = state + return activeTab && activeTab.url && getOriginFromUrl(activeTab.url) +} + +function getLastConnectedInfo (state) { + const { permissionsHistory = {} } = state.metamask + const lastConnectedInfoData = Object.keys(permissionsHistory).reduce((acc, origin) => { + const ethAccountsHistory = JSON.parse(JSON.stringify(permissionsHistory[origin]['eth_accounts'])) + return { + ...acc, + [origin]: ethAccountsHistory.accounts, + } + }, {}) + return lastConnectedInfoData +} diff --git a/ui/app/store/actions.js b/ui/app/store/actions.js index 19c9bfaea..ee55996a5 100644 --- a/ui/app/store/actions.js +++ b/ui/app/store/actions.js @@ -17,7 +17,7 @@ const { hasUnconfirmedTransactions } = require('../helpers/utils/confirm-tx.util const gasDuck = require('../ducks/gas/gas.duck') const WebcamUtils = require('../../lib/webcam-utils') -var actions = { +const actions = { _setBackgroundConnection: _setBackgroundConnection, GO_HOME: 'GO_HOME', @@ -328,7 +328,6 @@ var actions = { setUseNativeCurrencyAsPrimaryCurrencyPreference, setShowFiatConversionOnTestnetsPreference, setAutoLogoutTimeLimit, - unsetMigratedPrivacyMode, // Onboarding setCompletedOnboarding, @@ -352,9 +351,12 @@ var actions = { createSpeedUpTransaction, createRetryTransaction, - approveProviderRequestByOrigin, - rejectProviderRequestByOrigin, - clearApprovedOrigins, + // Permissions + approvePermissionsRequest, + clearPermissions, + rejectPermissionsRequest, + removePermissionsFor, + legacyExposeAccounts, setFirstTimeFlowType, SET_FIRST_TIME_FLOW_TYPE: 'SET_FIRST_TIME_FLOW_TYPE', @@ -394,11 +396,18 @@ var actions = { turnThreeBoxSyncingOnAndInitialize, tryReverseResolveAddress, + + getRequestAccountTabIds, + getCurrentWindowTab, + SET_REQUEST_ACCOUNT_TABS: 'SET_REQUEST_ACCOUNT_TABS', + SET_CURRENT_WINDOW_TAB: 'SET_CURRENT_WINDOW_TAB', + getOpenMetamaskTabsIds, + SET_OPEN_METAMASK_TAB_IDS: 'SET_OPEN_METAMASK_TAB_IDS', } module.exports = actions -var background = null +let background = null function _setBackgroundConnection (backgroundConnection) { background = backgroundConnection } @@ -675,7 +684,9 @@ function addNewKeyring (type, opts) { log.debug(`background.addNewKeyring`) background.addNewKeyring(type, opts, (err) => { dispatch(actions.hideLoadingIndication()) - if (err) return dispatch(actions.displayWarning(err.message)) + if (err) { + return dispatch(actions.displayWarning(err.message)) + } dispatch(actions.showAccountsPage()) }) } @@ -2168,7 +2179,7 @@ function requestExportAccount () { } function exportAccount (password, address) { - var self = this + const self = this return function (dispatch) { dispatch(self.showLoadingIndication()) @@ -2288,7 +2299,9 @@ function pairUpdate (coin) { dispatch(actions.hideWarning()) shapeShiftRequest('marketinfo', {pair: `${coin.toLowerCase()}_eth`}, (mktResponse) => { dispatch(actions.hideSubLoadingIndication()) - if (mktResponse.error) return dispatch(actions.displayWarning(mktResponse.error)) + if (mktResponse.error) { + return dispatch(actions.displayWarning(mktResponse.error)) + } dispatch({ type: actions.PAIR_UPDATE, value: { @@ -2300,13 +2313,15 @@ function pairUpdate (coin) { } function shapeShiftSubview () { - var pair = 'btc_eth' + const pair = 'btc_eth' return (dispatch) => { dispatch(actions.showSubLoadingIndication()) shapeShiftRequest('marketinfo', {pair}, (mktResponse) => { shapeShiftRequest('getcoins', {}, (response) => { dispatch(actions.hideSubLoadingIndication()) - if (mktResponse.error) return dispatch(actions.displayWarning(mktResponse.error)) + if (mktResponse.error) { + return dispatch(actions.displayWarning(mktResponse.error)) + } dispatch({ type: actions.SHAPESHIFT_SUBVIEW, value: { @@ -2324,8 +2339,10 @@ function coinShiftRquest (data, marketData) { dispatch(actions.showLoadingIndication()) shapeShiftRequest('shift', { method: 'POST', data}, (response) => { dispatch(actions.hideLoadingIndication()) - if (response.error) return dispatch(actions.displayWarning(response.error)) - var message = ` + if (response.error) { + return dispatch(actions.displayWarning(response.error)) + } + const message = ` Deposit your ${response.depositType} to the address below:` log.debug(`background.createShapeShiftTx`) background.createShapeShiftTx(response.deposit, response.depositType) @@ -2359,9 +2376,11 @@ function reshowQrCode (data, coin) { return (dispatch) => { dispatch(actions.showLoadingIndication()) shapeShiftRequest('marketinfo', {pair: `${coin.toLowerCase()}_eth`}, (mktResponse) => { - if (mktResponse.error) return dispatch(actions.displayWarning(mktResponse.error)) + if (mktResponse.error) { + return dispatch(actions.displayWarning(mktResponse.error)) + } - var message = [ + const message = [ `Deposit your ${coin} to the address below:`, `Deposit Limit: ${mktResponse.limit}`, `Deposit Minimum:${mktResponse.minimum}`, @@ -2374,10 +2393,10 @@ function reshowQrCode (data, coin) { } function shapeShiftRequest (query, options = {}, cb) { - var queryResponse, method + let queryResponse, method options.method ? method = options.method : method = 'GET' - var requestListner = function () { + const requestListner = function () { try { queryResponse = JSON.parse(this.responseText) if (cb) { @@ -2392,12 +2411,12 @@ function shapeShiftRequest (query, options = {}, cb) { } } - var shapShiftReq = new XMLHttpRequest() + const shapShiftReq = new XMLHttpRequest() shapShiftReq.addEventListener('load', requestListner) shapShiftReq.open(method, `https://shapeshift.io/${query}/${options.pair ? options.pair : ''}`, true) if (options.method === 'POST') { - var jsonObj = JSON.stringify(options.data) + const jsonObj = JSON.stringify(options.data) shapShiftReq.setRequestHeader('Content-Type', 'application/json') return shapShiftReq.send(jsonObj) } else { @@ -2698,24 +2717,59 @@ function setPendingTokens (pendingTokens) { } } -function approveProviderRequestByOrigin (origin) { +// Permissions + +/** + * Approves the permission requests with the given IDs. + * @param {string} requestId - The id of the permissions request. + * @param {string[]} accounts - The accounts to expose, if any. + */ +function approvePermissionsRequest (requestId, accounts) { return () => { - background.approveProviderRequestByOrigin(origin) + background.approvePermissionsRequest(requestId, accounts) } } -function rejectProviderRequestByOrigin (origin) { +/** + * Rejects the permission requests with the given IDs. + * @param {Array} requestId + */ +function rejectPermissionsRequest (requestId) { return () => { - background.rejectProviderRequestByOrigin(origin) + background.rejectPermissionsRequest(requestId) } } -function clearApprovedOrigins () { +/** + * Exposes the given account(s) to the given origin. + * Call ONLY as a result of direct user action. + */ +function legacyExposeAccounts (origin, accounts) { return () => { - background.clearApprovedOrigins() + return background.legacyExposeAccounts(origin, accounts) } } +/** + * Clears the given permissions for the given origin. + */ +function removePermissionsFor (domains) { + return () => { + background.removePermissionsFor(domains) + } +} + +/** + * Clears all permissions for all domains. + */ +function clearPermissions () { + return () => { + background.clearPermissions() + } +} + +// //// + function setFirstTimeFlowType (type) { return (dispatch) => { log.debug(`background.setFirstTimeFlowType`) @@ -2826,12 +2880,6 @@ function getTokenParams (tokenAddress) { } } -function unsetMigratedPrivacyMode () { - return () => { - background.unsetMigratedPrivacyMode() - } -} - function setSeedPhraseBackedUp (seedPhraseBackupState) { return (dispatch) => { log.debug(`background.setSeedPhraseBackedUp`) @@ -2969,3 +3017,46 @@ function getNextNonce () { }) } } + +function setRequestAccountTabIds (requestAccountTabIds) { + return { + type: actions.SET_REQUEST_ACCOUNT_TABS, + value: requestAccountTabIds, + } +} + +function getRequestAccountTabIds () { + return async (dispatch) => { + const requestAccountTabIds = await pify(background.getRequestAccountTabIds).call(background) + dispatch(setRequestAccountTabIds(requestAccountTabIds)) + } +} + +function setOpenMetamaskTabsIDs (openMetaMaskTabIDs) { + return { + type: actions.SET_OPEN_METAMASK_TAB_IDS, + value: openMetaMaskTabIDs, + } +} + +function getOpenMetamaskTabsIds () { + return async (dispatch) => { + const openMetaMaskTabIDs = await pify(background.getOpenMetamaskTabsIds).call(background) + dispatch(setOpenMetamaskTabsIDs(openMetaMaskTabIDs)) + } +} + +function setCurrentWindowTab (currentWindowTab) { + return { + type: actions.SET_CURRENT_WINDOW_TAB, + value: currentWindowTab, + } +} + + +function getCurrentWindowTab () { + return async (dispatch) => { + const currentWindowTab = await global.platform.currentTab() + dispatch(setCurrentWindowTab(currentWindowTab)) + } +} diff --git a/ui/index.js b/ui/index.js index f06b31f6c..033fe3ef9 100644 --- a/ui/index.js +++ b/ui/index.js @@ -1,5 +1,5 @@ +import React from 'react' const render = require('react-dom').render -const h = require('react-hyperscript') const Root = require('./app/pages') const actions = require('./app/store/actions') const configureStore = require('./app/store/store') @@ -13,11 +13,13 @@ module.exports = launchMetamaskUi log.setLevel(global.METAMASK_DEBUG ? 'debug' : 'warn') function launchMetamaskUi (opts, cb) { - var {backgroundConnection} = opts + const {backgroundConnection} = opts actions._setBackgroundConnection(backgroundConnection) // check if we are unlocked first backgroundConnection.getState(function (err, metamaskState) { - if (err) return cb(err) + if (err) { + return cb(err) + } startApp(metamaskState, backgroundConnection, opts) .then((store) => { cb(null, store) @@ -27,7 +29,9 @@ function launchMetamaskUi (opts, cb) { async function startApp (metamaskState, backgroundConnection, opts) { // parse opts - if (!metamaskState.featureFlags) metamaskState.featureFlags = {} + if (!metamaskState.featureFlags) { + metamaskState.featureFlags = {} + } const currentLocaleMessages = metamaskState.currentLocale ? await fetchLocale(metamaskState.currentLocale) @@ -92,11 +96,11 @@ async function startApp (metamaskState, backgroundConnection, opts) { // start app render( - h(Root, { - // inject initial state - store: store, - } - ), opts.container) + , + opts.container, + ) return store } diff --git a/ui/lib/icon-factory.js b/ui/lib/icon-factory.js index 2ea943297..cbaa496cd 100644 --- a/ui/lib/icon-factory.js +++ b/ui/lib/icon-factory.js @@ -1,4 +1,4 @@ -var iconFactory +let iconFactory const isValidAddress = require('ethereumjs-util').isValidAddress const { checksumAddress } = require('../app/helpers/utils/util') const contractMap = require('eth-contract-metadata') @@ -26,18 +26,18 @@ IconFactory.prototype.iconForAddress = function (address, diameter) { // returns svg dom element IconFactory.prototype.generateIdenticonSvg = function (address, diameter) { - var cacheId = `${address}:${diameter}` + const cacheId = `${address}:${diameter}` // check cache, lazily generate and populate cache - var identicon = this.cache[cacheId] || (this.cache[cacheId] = this.generateNewIdenticon(address, diameter)) + const identicon = this.cache[cacheId] || (this.cache[cacheId] = this.generateNewIdenticon(address, diameter)) // create a clean copy so you can modify it - var cleanCopy = identicon.cloneNode(true) + const cleanCopy = identicon.cloneNode(true) return cleanCopy } // creates a new identicon IconFactory.prototype.generateNewIdenticon = function (address, diameter) { - var numericRepresentation = jsNumberForAddress(address) - var identicon = this.jazzicon(diameter, numericRepresentation) + const numericRepresentation = jsNumberForAddress(address) + const identicon = this.jazzicon(diameter, numericRepresentation) return identicon } @@ -58,8 +58,8 @@ function imageElFor (address) { } function jsNumberForAddress (address) { - var addr = address.slice(2, 10) - var seed = parseInt(addr, 16) + const addr = address.slice(2, 10) + const seed = parseInt(addr, 16) return seed } diff --git a/ui/lib/persistent-form.js b/ui/lib/persistent-form.js index d4dc20b03..112e4415b 100644 --- a/ui/lib/persistent-form.js +++ b/ui/lib/persistent-form.js @@ -15,7 +15,7 @@ PersistentForm.prototype.componentDidMount = function () { const fields = document.querySelectorAll('[data-persistent-formid]') const store = this.getPersistentStore() - for (var i = 0; i < fields.length; i++) { + for (let i = 0; i < fields.length; i++) { const field = fields[i] const key = field.getAttribute('data-persistent-formid') const cached = store[key] @@ -52,7 +52,7 @@ PersistentForm.prototype.persistentFieldDidUpdate = function (event) { PersistentForm.prototype.componentWillUnmount = function () { const fields = document.querySelectorAll('[data-persistent-formid]') - for (var i = 0; i < fields.length; i++) { + for (let i = 0; i < fields.length; i++) { const field = fields[i] field.removeEventListener(eventName, this.persistentFieldDidUpdate.bind(this)) } diff --git a/yarn.lock b/yarn.lock index f83ad6d26..edd62920d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1996,6 +1996,18 @@ events "^2.0.0" hdkey "0.8.0" +"@metamask/forwarder@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@metamask/forwarder/-/forwarder-1.0.0.tgz#3e321022a36561cc6e7b7c84df25f600925f4d95" + integrity sha512-ufgPndhZz0oNhRrixiR6cXH/HwtFwurWvbrU8zAZsFnf1hB4L2VB2Wey/P1wStIx+BJJQjyROvCDyPDoz4ny1A== + +"@metamask/onboarding@^0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@metamask/onboarding/-/onboarding-0.1.2.tgz#d5126cbb5e593d782645d6236c497e27bd38d3c4" + integrity sha512-+85Z5OxckGuYr5cCoMlpxASu9geJBMYvwkNWqa5qDDEYKZ8eNXHsADcVYFsvBhxFcf87dC7ty1kWljYVEfTIIA== + dependencies: + bowser "^2.5.4" + "@mrmlnc/readdir-enhanced@^2.2.1": version "2.2.1" resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" @@ -3571,11 +3583,6 @@ ansi-red@^0.1.1: dependencies: ansi-wrap "0.1.0" -ansi-regex@^0.2.0, ansi-regex@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-0.2.1.tgz#0d8e946967a3d8143f93e24e298525fc1b2235f9" - integrity sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk= - ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" @@ -3591,11 +3598,6 @@ ansi-regex@^4.1.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== -ansi-styles@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-1.1.0.tgz#eaecbf66cd706882760b2f4691582b8f55d7a7de" - integrity sha1-6uy/Zs1waIJ2Cy9GkVgrj1XXp94= - ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" @@ -4036,10 +4038,11 @@ assert-plus@1.0.0, assert-plus@^1.0.0: integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= assert@^1.1.1, assert@^1.3.0, assert@^1.4.0, assert@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91" - integrity sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE= + version "1.5.0" + resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" + integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA== dependencies: + object-assign "^4.1.1" util "0.10.3" assertion-error@^1.0.1: @@ -5651,6 +5654,11 @@ bowser@^1.7.3: resolved "https://registry.yarnpkg.com/bowser/-/bowser-1.9.3.tgz#6643ae4d783f31683f6d23156976b74183862162" integrity sha512-/gp96UlcFw5DbV2KQPCqTqi0Mb9gZRyDAHiDsGEH+4B/KOQjeoE5lM1PxlVX8DQDvfEfitmC1rW2Oy8fk/XBDg== +bowser@^2.5.4: + version "2.7.0" + resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.7.0.tgz#96eab1fa07fab08c1ec4c75977a7c8ddf8e0fe1f" + integrity sha512-aIlMvstvu8x+34KEiOHD3AsBgdrzg6sxALYiukOWhFvGMbQI6TRP/iY0LMhUrHs56aD6P1G0Z7h45PUJaa5m9w== + boxen@^1.2.1: version "1.3.0" resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b" @@ -6446,17 +6454,6 @@ chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4. escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.5.1.tgz#663b3a648b68b55d04690d49167aa837858f2174" - integrity sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ= - dependencies: - ansi-styles "^1.1.0" - escape-string-regexp "^1.0.0" - has-ansi "^0.1.0" - strip-ansi "^0.3.0" - supports-color "^0.2.0" - chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" @@ -7034,7 +7031,7 @@ comma-separated-tokens@^1.0.0: resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.7.tgz#419cd7fb3258b1ed838dc0953167a25e152f5b59" integrity sha512-Jrx3xsP4pPv4AwJUDWY9wOXGtwPXARej6Xd99h4TUGotmf8APuquKMpK+dnD3UgyxK7OEWaisjZz+3b5jtL6xQ== -commander@2, commander@2.11.0, commander@^2.3.0, commander@^2.5.0, commander@^2.6.0: +commander@2, commander@2.11.0, commander@^2.5.0, commander@^2.6.0: version "2.11.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" integrity sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ== @@ -7248,6 +7245,11 @@ contains-path@^0.1.0: resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo= +content-disposition@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" + integrity sha1-DPaLud318r55YcOoUXjLhdunjLQ= + content-disposition@0.5.3, content-disposition@^0.5.2: version "0.5.3" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" @@ -9609,7 +9611,7 @@ escape-html@^1.0.3, escape-html@~1.0.3: resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= -escape-string-regexp@1.0.5, escape-string-regexp@^1.0.0, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.3, escape-string-regexp@^1.0.5: +escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.3, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= @@ -10084,7 +10086,7 @@ eth-hd-keyring@^3.4.0: events "^1.1.1" xtend "^4.0.1" -eth-json-rpc-errors@^1.0.1, eth-json-rpc-errors@^1.1.0: +eth-json-rpc-errors@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/eth-json-rpc-errors/-/eth-json-rpc-errors-1.1.0.tgz#2a4291fb20c0483c99b53286a814ed14ca4efb2e" integrity sha512-AAA76BmwwSR5Mws+ivZUYxoDwMygDuMWxSTEmqDXhRPTExSWe5wuJLT/rSfvPSy9+owSudy67JmyRQ02RAOOYQ== @@ -10098,6 +10100,13 @@ eth-json-rpc-errors@^2.0.0: dependencies: fast-safe-stringify "^2.0.6" +eth-json-rpc-errors@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/eth-json-rpc-errors/-/eth-json-rpc-errors-2.0.2.tgz#c1965de0301fe941c058e928bebaba2e1285e3c4" + integrity sha512-uBCRM2w2ewusRHGxN8JhcuOb2RN3ueAOYH/0BhqdFmQkZx5lj5+fLKTz0mIVOzd4FG5/kUksCzCD7eTEim6gaA== + dependencies: + fast-safe-stringify "^2.0.6" + eth-json-rpc-filters@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/eth-json-rpc-filters/-/eth-json-rpc-filters-4.1.1.tgz#15277c66790236d85f798f4d7dc6bab99a798cd2" @@ -11328,6 +11337,13 @@ fast-safe-stringify@^2.0.6, fast-safe-stringify@^2.0.7: resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743" integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA== +fast-url-parser@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/fast-url-parser/-/fast-url-parser-1.1.3.tgz#f4af3ea9f34d8a271cf58ad2b3759f431f0b318d" + integrity sha1-9K8+qfNNiicc9YrSs3WfQx8LMY0= + dependencies: + punycode "^1.3.2" + fast-write-atomic@~0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/fast-write-atomic/-/fast-write-atomic-0.2.1.tgz#7ee8ef0ce3c1f531043c09ae8e5143361ab17ede" @@ -11462,11 +11478,6 @@ file-loader@^3.0.1: loader-utils "^1.0.2" schema-utils "^1.0.0" -file-size@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/file-size/-/file-size-0.0.5.tgz#057d43c3a3ed735da3f90d6052ab380f1e6d5e3b" - integrity sha1-BX1Dw6Ptc12j+Q1gUqs4Dx5tXjs= - file-system-cache@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/file-system-cache/-/file-system-cache-1.0.5.tgz#84259b36a2bbb8d3d6eb1021d3132ffe64cfff4f" @@ -12180,7 +12191,7 @@ fuse.js@^3.4.4: resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-3.4.5.tgz#8954fb43f9729bd5dbcb8c08f251db552595a7a6" integrity sha512-s9PGTaQIkT69HaeoTVjwGsLfb8V8ScJLx5XGFcKHg0MqLUH/UZ4EKOtqtXX9k7AFqCGxD1aJmYb8Q5VYDibVRQ== -gaba@^1.9.3: +gaba@^1.6.0, gaba@^1.9.3: version "1.9.3" resolved "https://registry.yarnpkg.com/gaba/-/gaba-1.9.3.tgz#4e0e106f3640930f1f06ffe72546903b4c51813e" integrity sha512-zC9CpaksncAT9SSc4QAxozUE+SKIWN+r9YwhjAJoSeh9joqPJsXlJOHg1/CrHABpvN68QdE00wAYSabYM02EqQ== @@ -13308,13 +13319,6 @@ har-validator@~5.1.0: ajv "^6.5.5" har-schema "^2.0.0" -has-ansi@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-0.1.0.tgz#84f265aae8c0e6a88a12d7022894b7568894c62e" - integrity sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4= - dependencies: - ansi-regex "^0.2.0" - has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" @@ -14270,6 +14274,11 @@ interpret@^1.2.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296" integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw== +intersect-objects@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/intersect-objects/-/intersect-objects-1.0.0.tgz#b7630d28994b89b0f04d44728106136549ce816e" + integrity sha512-MS1xypHKJhWopnJgn4IbitVvt2vFy2KjINQJAPhAtDejZ+ZbMDfyPc6JsS/mWFRt9Eoku4A4usE4f2loEOoeKQ== + into-stream@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/into-stream/-/into-stream-3.1.0.tgz#96fb0a936c12babd6ff1752a17d05616abd094c6" @@ -15891,6 +15900,16 @@ json-rpc-engine@^5.1.3, json-rpc-engine@^5.1.6: promise-to-callback "^1.0.0" safe-event-emitter "^1.0.1" +json-rpc-engine@^5.1.5: + version "5.1.8" + resolved "https://registry.yarnpkg.com/json-rpc-engine/-/json-rpc-engine-5.1.8.tgz#5ba0147ce571899bbaa7133ffbc05317c34a3c7f" + integrity sha512-vTBSDEPJV1fPAsbm2g5sEuPjsgLdiab2f1CTn2PyRr8nxggUpA996PDlNQDsM0gnrA99F8KIBLq2nIKrOFl1Mg== + dependencies: + async "^2.0.1" + eth-json-rpc-errors "^2.0.1" + promise-to-callback "^1.0.0" + safe-event-emitter "^1.0.1" + json-rpc-error@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/json-rpc-error/-/json-rpc-error-2.0.0.tgz#a7af9c202838b5e905c7250e547f1aff77258a02" @@ -17619,12 +17638,12 @@ lodash.uniqby@^4.7.0: resolved "https://registry.yarnpkg.com/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz#d99c07a669e9e6d24e1362dfe266c67616af1302" integrity sha1-2ZwHpmnp5tJOE2Lf4mbGdhavEwI= -lodash@4.17.14, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.8.0, lodash@~4.17.10, lodash@~4.17.2, lodash@~4.17.4: +lodash@4.17.14, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.8.0, lodash@~4.17.10, lodash@~4.17.2, lodash@~4.17.4: version "4.17.14" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.14.tgz#9ce487ae66c96254fe20b599f21b6816028078ba" integrity sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw== -lodash@=3.10.1, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15: +lodash@=3.10.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== @@ -18236,12 +18255,14 @@ mersenne-twister@^1.0.1: resolved "https://registry.yarnpkg.com/mersenne-twister/-/mersenne-twister-1.1.0.tgz#f916618ee43d7179efcf641bec4531eb9670978a" integrity sha1-+RZhjuQ9cXnvz2Qb7EUx65Zwl4o= -metamask-inpage-provider@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/metamask-inpage-provider/-/metamask-inpage-provider-3.0.0.tgz#3b9d4bae6f67962b6a7b1a9ee1efaf424f67b6f4" - integrity sha512-44bBCbQwcFF/XGaXSweCWHJaslKhJEFgvcHdxZf9Fm1QfK7VN4U3iAI0BVOLAIkRg0xV3w7xYGLpx2cM1BU7Qw== +metamask-inpage-provider@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/metamask-inpage-provider/-/metamask-inpage-provider-4.0.2.tgz#50d9e46b5fdd6610ce185a165004e3c6b762dbb3" + integrity sha512-GXoMa7rP+fx9CriCA+RPHjvJJpfy9531eRdMvbDKv0q95/1pvtzYkj6BdzjxtbM91n4zYl6tmeKDILu+le9Qog== dependencies: - json-rpc-engine "^5.1.3" + eth-json-rpc-errors "^2.0.0" + fast-deep-equal "^2.0.1" + json-rpc-engine "^5.1.5" json-rpc-middleware-stream "^2.1.1" loglevel "^1.6.1" obj-multiplex "^1.0.0" @@ -18331,11 +18352,23 @@ mime-db@^1.28.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.41.0.tgz#9110408e1f6aa1b34aef51f2c9df3caddf46b6a0" integrity sha512-B5gxBI+2K431XW8C2rcc/lhppbuji67nf9v39eH8pkWoZDxnAL0PxdpH32KYRScniF8qDHBDlI+ipgg5WrCUYw== +mime-db@~1.33.0: + version "1.33.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" + integrity sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ== + mime-db@~1.38.0: version "1.38.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.38.0.tgz#1a2aab16da9eb167b49c6e4df2d9c68d63d8e2ad" integrity sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg== +mime-types@2.1.18: + version "2.1.18" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" + integrity sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ== + dependencies: + mime-db "~1.33.0" + mime-types@^2.1.12, mime-types@^2.1.21, mime-types@~2.1.16, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24: version "2.1.24" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81" @@ -18355,7 +18388,7 @@ mime@1.6.0, mime@^1.6.0: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== -mime@^1.2.11, mime@^1.4.1: +mime@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" integrity sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ== @@ -18901,6 +18934,11 @@ nanoid@^2.0.0: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-2.0.3.tgz#dde999e173bc9d7bd2ee2746b89909ade98e075e" integrity sha512-NbaoqdhIYmY6FXDRB4eYtDVC9Z9eCbn8TyaiC16LNKtpPv/aqa0tOPD8y6gNE4yUNnaZ7LLhYtXOev/6+cBtfw== +nanoid@^2.1.6: + version "2.1.6" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-2.1.6.tgz#0665418f692e54cf44f34d4010761f3240a03314" + integrity sha512-2NDzpiuEy3+H0AVtdt8LoFi7PnqkOnIzYmJQp7xsEU6VexLluHQwKREuiz57XaQC5006seIadPrIZJhyS2n7aw== + nanomatch@^1.2.9: version "1.2.9" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.9.tgz#879f7150cb2dab7a471259066c104eee6e0fa7c2" @@ -19786,13 +19824,6 @@ opn@5.4.0: dependencies: is-wsl "^1.1.0" -opn@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/opn/-/opn-5.2.0.tgz#71fdf934d6827d676cecbea1531f95d354641225" - integrity sha512-Jd/GpzPyHF4P2/aNOVmS3lfMSWV9J7cOhCG1s08XCEAsPkB7lp6ddiU0J7XzyQRDUh8BqJ7PchfINjR8jyofRQ== - dependencies: - is-wsl "^1.1.0" - optimist@0.6.x, optimist@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" @@ -20463,7 +20494,7 @@ path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= -path-is-inside@^1.0.1, path-is-inside@^1.0.2: +path-is-inside@1.0.2, path-is-inside@^1.0.1, path-is-inside@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= @@ -20500,6 +20531,11 @@ path-to-regexp@0.1.7: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= +path-to-regexp@2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.2.1.tgz#90b617025a16381a879bc82a38d4e8bdeb2bcf45" + integrity sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ== + path-to-regexp@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d" @@ -21948,6 +21984,11 @@ randomhex@0.1.5: resolved "https://registry.yarnpkg.com/randomhex/-/randomhex-0.1.5.tgz#baceef982329091400f2a2912c6cd02f1094f585" integrity sha1-us7vmCMpCRQA8qKRLGzQLxCU9YU= +range-parser@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" + integrity sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4= + range-parser@^1.2.0, range-parser@^1.2.1, range-parser@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" @@ -23677,6 +23718,21 @@ rn-host-detect@^1.1.5: resolved "https://registry.yarnpkg.com/rn-host-detect/-/rn-host-detect-1.1.5.tgz#fbecb982b73932f34529e97932b9a63e58d8deb6" integrity sha512-ufk2dFT3QeP9HyZ/xTuMtW27KnFy815CYitJMqQm+pgG3ZAtHBsrU8nXizNKkqXGy3bQmhEoloVbrfbvMJMqkg== +rpc-cap@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/rpc-cap/-/rpc-cap-1.0.1.tgz#c19f6651d9d003256c73831422e0bd60b4fa8b55" + integrity sha512-M75F5IfohYkwGvitWmstimP9OL+9h10m1ZRC2zCB1Nli4EPzL8n5re58xlrcOnwOO38FdSSPfcwcCzMuVT8K2g== + dependencies: + clone "^2.1.2" + eth-json-rpc-errors "^2.0.0" + fast-deep-equal "^2.0.1" + gaba "^1.6.0" + intersect-objects "^1.0.0" + is-subset "^0.1.1" + json-rpc-engine "^5.1.3" + obs-store "^4.0.3" + uuid "^3.3.2" + rsa-pem-to-jwk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/rsa-pem-to-jwk/-/rsa-pem-to-jwk-1.1.3.tgz#245e76bdb7e7234cfee7ca032d31b54c38fab98e" @@ -23812,6 +23868,11 @@ safe-json-parse@~1.0.1: resolved "https://registry.yarnpkg.com/safe-json-parse/-/safe-json-parse-1.0.1.tgz#3e76723e38dfdda13c9b1d29a1e07ffee4b30b57" integrity sha1-PnZyPjjf3aE8mx0poeB//uSzC1c= +safe-json-stringify@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz#356e44bc98f1f93ce45df14bcd7c01cda86e0afd" + integrity sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg== + safe-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" @@ -24168,6 +24229,20 @@ serve-favicon@^2.5.0: parseurl "~1.3.2" safe-buffer "5.1.1" +serve-handler@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/serve-handler/-/serve-handler-6.1.2.tgz#f05b0421a313fff2d257838cba00cbcc512cd2b6" + integrity sha512-RFh49wX7zJmmOVDcIjiDSJnMH+ItQEvyuYLYuDBVoA/xmQSCuj+uRmk1cmBB5QQlI3qOiWKp6p4DUGY+Z5AB2A== + dependencies: + bytes "3.0.0" + content-disposition "0.5.2" + fast-url-parser "1.1.3" + mime-types "2.1.18" + minimatch "3.0.4" + path-is-inside "1.0.2" + path-to-regexp "2.2.1" + range-parser "1.2.0" + serve-static@1.14.1: version "1.14.1" resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" @@ -25068,17 +25143,6 @@ static-module@^2.2.0: static-eval "^2.0.0" through2 "~2.0.3" -static-server@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/static-server/-/static-server-2.2.1.tgz#49e3cae2a001736b0ee9e95d21d3d843fc95efaa" - integrity sha512-j5eeW6higxYNmXMIT8iHjsdiViTpQDthg7o+SHsRtqdbxscdHqBHXwrXjHC8hL3F0Tsu34ApUpDkwzMBPBsrLw== - dependencies: - chalk "^0.5.1" - commander "^2.3.0" - file-size "0.0.5" - mime "^1.2.11" - opn "^5.2.0" - "statuses@>= 1.3.1 < 2": version "1.3.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" @@ -25383,13 +25447,6 @@ strip-ansi@5.2.0, strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" -strip-ansi@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-0.3.0.tgz#25f48ea22ca79187f3174a4db8759347bb126220" - integrity sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA= - dependencies: - ansi-regex "^0.2.1" - strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" @@ -25659,11 +25716,6 @@ supports-color@4.4.0: dependencies: has-flag "^2.0.0" -supports-color@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-0.2.0.tgz#d92de2694eb3f67323973d7ae3d8b55b4c22190a" - integrity sha1-2S3iaU6z9nMjlz1649i1W0wiGQo= - supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -27237,7 +27289,7 @@ uuid@3.2.1: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14" integrity sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA== -uuid@3.3.2, uuid@^3.0.1, uuid@^3.1.0, uuid@^3.2.1, uuid@^3.2.2, uuid@^3.3.2: +uuid@3.3.2, uuid@^3.0.1, uuid@^3.1.0, uuid@^3.2.1, uuid@^3.2.2: version "3.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== @@ -27247,7 +27299,7 @@ uuid@^2.0.1: resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a" integrity sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho= -uuid@^3.3.3: +uuid@^3.3.2, uuid@^3.3.3: version "3.3.3" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866" integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ== From 21b7e42a75655fff83c3033d9f3c3c9c0e5746fe Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Mon, 1 Jun 2020 17:13:46 -0300 Subject: [PATCH 67/70] Remove redundant entries from `yarn.lock` For some reason, Yarn left these redundant entries in the lockfile after merging `develop` into `master`. --- yarn.lock | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/yarn.lock b/yarn.lock index 4d02ef8cd..498c93cc3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10098,13 +10098,6 @@ eth-json-rpc-errors@^2.0.1, eth-json-rpc-errors@^2.0.2: dependencies: fast-safe-stringify "^2.0.6" -eth-json-rpc-errors@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/eth-json-rpc-errors/-/eth-json-rpc-errors-2.0.2.tgz#c1965de0301fe941c058e928bebaba2e1285e3c4" - integrity sha512-uBCRM2w2ewusRHGxN8JhcuOb2RN3ueAOYH/0BhqdFmQkZx5lj5+fLKTz0mIVOzd4FG5/kUksCzCD7eTEim6gaA== - dependencies: - fast-safe-stringify "^2.0.6" - eth-json-rpc-filters@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/eth-json-rpc-filters/-/eth-json-rpc-filters-4.1.1.tgz#15277c66790236d85f798f4d7dc6bab99a798cd2" @@ -15995,16 +15988,6 @@ json-rpc-engine@^5.1.3, json-rpc-engine@^5.1.5, json-rpc-engine@^5.1.8: promise-to-callback "^1.0.0" safe-event-emitter "^1.0.1" -json-rpc-engine@^5.1.5: - version "5.1.8" - resolved "https://registry.yarnpkg.com/json-rpc-engine/-/json-rpc-engine-5.1.8.tgz#5ba0147ce571899bbaa7133ffbc05317c34a3c7f" - integrity sha512-vTBSDEPJV1fPAsbm2g5sEuPjsgLdiab2f1CTn2PyRr8nxggUpA996PDlNQDsM0gnrA99F8KIBLq2nIKrOFl1Mg== - dependencies: - async "^2.0.1" - eth-json-rpc-errors "^2.0.1" - promise-to-callback "^1.0.0" - safe-event-emitter "^1.0.1" - json-rpc-error@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/json-rpc-error/-/json-rpc-error-2.0.0.tgz#a7af9c202838b5e905c7250e547f1aff77258a02" From f3d64a1851ca3c5efcca2f7d4fb119e4b59a7ed3 Mon Sep 17 00:00:00 2001 From: MetaMask Bot Date: Mon, 1 Jun 2020 21:40:56 +0000 Subject: [PATCH 68/70] Version v8.0.0 --- CHANGELOG.md | 2 ++ app/manifest/_base.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22e5de6a9..29e437922 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog ## Current Develop Branch + +## 8.0.0 Mon Jun 01 2020 - [#7912](https://github.com/MetaMask/metamask-extension/pull/7912): Disable import button for empty string/file - [#8246](https://github.com/MetaMask/metamask-extension/pull/8246): Make seed phrase import case-insensitive - [#8502](https://github.com/MetaMask/metamask-extension/pull/8502): Add support for IPFS address resolution diff --git a/app/manifest/_base.json b/app/manifest/_base.json index 03acd5361..83ad55d90 100644 --- a/app/manifest/_base.json +++ b/app/manifest/_base.json @@ -1,7 +1,7 @@ { "name": "__MSG_appName__", "short_name": "__MSG_appName__", - "version": "7.7.9", + "version": "8.0.0", "manifest_version": 2, "author": "https://metamask.io", "description": "__MSG_appDescription__", From 46e8f1b4a5cbee1fc4c3517e6e058fd4ea6cbb59 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Wed, 24 Jun 2020 17:32:21 -0300 Subject: [PATCH 69/70] Update changelog for v8 (#8849) Any changes that were development-focused, or that solely affected newly-introduced features, have been omitted. A duplicate '7.0' title has also been removed. It was accidentally added in a merge conflict. --- CHANGELOG.md | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 86 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29e437922..626a3a4c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,95 @@ ## Current Develop Branch -## 8.0.0 Mon Jun 01 2020 +## 8.0.0 Mon Jun 23 2020 +- [#7004](https://github.com/MetaMask/metamask-extension/pull/7004): Add permission system +- [#7261](https://github.com/MetaMask/metamask-extension/pull/7261): Search accounts by name +- [#7483](https://github.com/MetaMask/metamask-extension/pull/7483): Buffer 3 blocks before dropping a transaction +- [#7620](https://github.com/MetaMask/metamask-extension/pull/7620): Handle one specific permissions request per tab +- [#7686](https://github.com/MetaMask/metamask-extension/pull/7686): Add description to Reset Account in settings +- [#7362](https://github.com/MetaMask/metamask-extension/pull/7362): Allow custom IPFS gateway and use more secure default gateway +- [#7696](https://github.com/MetaMask/metamask-extension/pull/7696): Adjust colour of Reset Account button to reflect danger +- [#7602](https://github.com/MetaMask/metamask-extension/pull/7602): Support new onboarding library +- [#7672](https://github.com/MetaMask/metamask-extension/pull/7672): Update custom token symbol length restriction message +- [#7747](https://github.com/MetaMask/metamask-extension/pull/7747): Handle 'Enter' keypress on restore from seed screen +- [#7810](https://github.com/MetaMask/metamask-extension/pull/7810): Remove padding around advanced gas info icon +- [#7840](https://github.com/MetaMask/metamask-extension/pull/7840): Force background state update after removing an account +- [#7853](https://github.com/MetaMask/metamask-extension/pull/7853): Change "Log In/Out" terminology to "Unlock/Lock" +- [#7863](https://github.com/MetaMask/metamask-extension/pull/7863): Add mechanism to randomize seed phrase filename +- [#7933](https://github.com/MetaMask/metamask-extension/pull/7933): Sort seed phrase confirmation buttons alphabetically +- [#7987](https://github.com/MetaMask/metamask-extension/pull/7987): Add support for 24 word seed phrases +- [#7971](https://github.com/MetaMask/metamask-extension/pull/7971): Use contact name instead of address during send flow +- [#8050](https://github.com/MetaMask/metamask-extension/pull/8050): Add title attribute to transaction title +- [#7831](https://github.com/MetaMask/metamask-extension/pull/7831): Implement encrypt/decrypt feature +- [#8125](https://github.com/MetaMask/metamask-extension/pull/8125): Add setting for disabling Eth Phishing Detection +- [#8148](https://github.com/MetaMask/metamask-extension/pull/8148): Prevent external domains from submitting more than one perm request at a time +- [#8149](https://github.com/MetaMask/metamask-extension/pull/8149): Wait for extension unlock before processing eth_requestAccounts +- [#8201](https://github.com/MetaMask/metamask-extension/pull/8201): Add Idle Timeout for Sync with mobile +- [#8247](https://github.com/MetaMask/metamask-extension/pull/8247): Update Italian translation +- [#8246](https://github.com/MetaMask/metamask-extension/pull/8246): Make seed phrase import case-insensitive +- [#8254](https://github.com/MetaMask/metamask-extension/pull/8254): Convert Connected Sites page to modal +- [#8259](https://github.com/MetaMask/metamask-extension/pull/8259): Update token cell to show inline stale balance warning +- [#8264](https://github.com/MetaMask/metamask-extension/pull/8264): Move asset list to home tab on small screens +- [#8270](https://github.com/MetaMask/metamask-extension/pull/8270): Connected status indicator +- [#8078](https://github.com/MetaMask/metamask-extension/pull/8078): Allow selecting multiple accounts during connect flow +- [#8318](https://github.com/MetaMask/metamask-extension/pull/8318): Focus the notification popup if it's already open +- [#8356](https://github.com/MetaMask/metamask-extension/pull/8356): Position notification relative to last focused window +- [#8358](https://github.com/MetaMask/metamask-extension/pull/8358): Close notification UI if no unapproved confirmations +- [#8293](https://github.com/MetaMask/metamask-extension/pull/8293): Add popup explaining connection indicator to existing users +- [#8435](https://github.com/MetaMask/metamask-extension/pull/8435): Correctly detect changes to background state - [#7912](https://github.com/MetaMask/metamask-extension/pull/7912): Disable import button for empty string/file - [#8246](https://github.com/MetaMask/metamask-extension/pull/8246): Make seed phrase import case-insensitive +- [#8312](https://github.com/MetaMask/metamask-extension/pull/8312): Alert user upon switching to unconnected account +- [#8445](https://github.com/MetaMask/metamask-extension/pull/8445): Only updating pending transactions upon block update +- [#8467](https://github.com/MetaMask/metamask-extension/pull/8467): Fix firefox popup location +- [#8486](https://github.com/MetaMask/metamask-extension/pull/8486): Prevent race condition where transaction value set in UI is overwritten +- [#8490](https://github.com/MetaMask/metamask-extension/pull/8490): Fix default gas race condition +- [#8491](https://github.com/MetaMask/metamask-extension/pull/8491): Update tokens after importing account +- [#8496](https://github.com/MetaMask/metamask-extension/pull/8496): Enable disconnecting a single account or all accounts - [#8502](https://github.com/MetaMask/metamask-extension/pull/8502): Add support for IPFS address resolution +- [#8419](https://github.com/MetaMask/metamask-extension/pull/8419): Add version dimension to metrics event +- [#8508](https://github.com/MetaMask/metamask-extension/pull/8508): Open notification UI when eth_requestAccounts waits for unlock +- [#8533](https://github.com/MetaMask/metamask-extension/pull/8533): Prevent negative values on gas inputs +- [#8550](https://github.com/MetaMask/metamask-extension/pull/8550): Allow disabling alerts +- [#8563](https://github.com/MetaMask/metamask-extension/pull/8563): Synchronously update transaction status +- [#8567](https://github.com/MetaMask/metamask-extension/pull/8567): Improve Spanish localized message +- [#8532](https://github.com/MetaMask/metamask-extension/pull/8532): Add switch to connected account alert +- [#8575](https://github.com/MetaMask/metamask-extension/pull/8575): Stop polling for recent blocks on custom networks when UI is closed +- [#8579](https://github.com/MetaMask/metamask-extension/pull/8579): Fix Matomo dimension IDs +- [#8592](https://github.com/MetaMask/metamask-extension/pull/8592): Handle trailing / in block explorer URLs +- [#8313](https://github.com/MetaMask/metamask-extension/pull/8313): Add Connected Accounts modal +- [#8609](https://github.com/MetaMask/metamask-extension/pull/8609): Sticky position the tabs at the top +- [#8634](https://github.com/MetaMask/metamask-extension/pull/8634): Define global `web3` as non-enumerable +- [#8601](https://github.com/MetaMask/metamask-extension/pull/8601): warn user when sending from different account +- [#8612](https://github.com/MetaMask/metamask-extension/pull/8612): Persist home tab state +- [#8564](https://github.com/MetaMask/metamask-extension/pull/8564): Implement new transaction list design +- [#8596](https://github.com/MetaMask/metamask-extension/pull/8596): Restrict the size of the permissions metadata store +- [#8654](https://github.com/MetaMask/metamask-extension/pull/8654): Update account options menu design +- [#8657](https://github.com/MetaMask/metamask-extension/pull/8657): Implement new fullscreen design +- [#8663](https://github.com/MetaMask/metamask-extension/pull/8663): Show hostname in the disconnect confirmation +- [#8665](https://github.com/MetaMask/metamask-extension/pull/8665): Make address display wider in Account Details +- [#8670](https://github.com/MetaMask/metamask-extension/pull/8670): Fix token `decimal` type +- [#8653](https://github.com/MetaMask/metamask-extension/pull/8653): Limit Dapp permissions to primary account +- [#8666](https://github.com/MetaMask/metamask-extension/pull/8666): Manually connect via the full connect flow +- [#8677](https://github.com/MetaMask/metamask-extension/pull/8677): Add metrics events for Wyre and CoinSwitch +- [#8680](https://github.com/MetaMask/metamask-extension/pull/8680): Fix connect hardware styling +- [#8689](https://github.com/MetaMask/metamask-extension/pull/8689): Fix create account form styling +- [#8702](https://github.com/MetaMask/metamask-extension/pull/8702): Fix tab content disappearing during scrolling on macOS Firefox +- [#8696](https://github.com/MetaMask/metamask-extension/pull/8696): Implement asset page +- [#8716](https://github.com/MetaMask/metamask-extension/pull/8716): Add nonce to transaction details +- [#8717](https://github.com/MetaMask/metamask-extension/pull/8717): Use URL origin instead of hostname for permission domains +- [#8747](https://github.com/MetaMask/metamask-extension/pull/8747): Fix account menu entry for imported accounts +- [#8768](https://github.com/MetaMask/metamask-extension/pull/8768): Permissions: Do not display HTTP/HTTPS URL schemes for unique hosts +- [#8730](https://github.com/MetaMask/metamask-extension/pull/8730): Hide seed phrase during Account Import +- [#8785](https://github.com/MetaMask/metamask-extension/pull/8785): Rename 'History' tab to 'Activity' +- [#8781](https://github.com/MetaMask/metamask-extension/pull/8781): use UI button for add token functionality +- [#8786](https://github.com/MetaMask/metamask-extension/pull/8786): Show fiat amounts inline on token transfers +- [#8789](https://github.com/MetaMask/metamask-extension/pull/8789): Warn users to only add custom networks that they trust +- [#8802](https://github.com/MetaMask/metamask-extension/pull/8802): Consolidate connected account alerts +- [#8810](https://github.com/MetaMask/metamask-extension/pull/8810): Remove all user- and translator-facing instances of 'dapp' +- [#8836](https://github.com/MetaMask/metamask-extension/pull/8836): Update method data when cached method data is empty +- [#8833](https://github.com/MetaMask/metamask-extension/pull/8833): Improve error handling when signature requested without a keyholder address +- [#8850](https://github.com/MetaMask/metamask-extension/pull/8850): Stop upper-casing exported private key ## 7.7.9 Tue Apr 28 2020 - [#8446](https://github.com/MetaMask/metamask-extension/pull/8446): Fix popup not opening @@ -72,7 +157,6 @@ - [#7558](https://github.com/MetaMask/metamask-extension/pull/7558): Use localized messages for NotificationModal buttons ## 7.7.0 Thu Nov 28 2019 [WITHDRAWN] -## 7.7.0 Thu Nov 28 2019 - [#7004](https://github.com/MetaMask/metamask-extension/pull/7004): Connect distinct accounts per site - [#7480](https://github.com/MetaMask/metamask-extension/pull/7480): Fixed link on root README.md - [#7482](https://github.com/MetaMask/metamask-extension/pull/7482): Update Wyre ETH purchase url From 4c17f6c9da0e01442470ca77f96c145eaba5a6ee Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Thu, 25 Jun 2020 22:50:15 -0300 Subject: [PATCH 70/70] Update changelog to include #8631 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 626a3a4c6..aa6b76751 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -91,6 +91,7 @@ - [#8836](https://github.com/MetaMask/metamask-extension/pull/8836): Update method data when cached method data is empty - [#8833](https://github.com/MetaMask/metamask-extension/pull/8833): Improve error handling when signature requested without a keyholder address - [#8850](https://github.com/MetaMask/metamask-extension/pull/8850): Stop upper-casing exported private key +- [#8631](https://github.com/MetaMask/metamask-extension/pull/8631): Include imported accounts in mobile sync ## 7.7.9 Tue Apr 28 2020 - [#8446](https://github.com/MetaMask/metamask-extension/pull/8446): Fix popup not opening