From 1ea41478b131d5b2f3788937988c12d6e9ac70fc Mon Sep 17 00:00:00 2001
From: Empowerful <45366397+Empowerful@users.noreply.github.com>
Date: Wed, 25 Sep 2019 18:07:39 -0400
Subject: [PATCH] Protect the seed by refactoring the application as two
independent processes running on a microkernel.
---
.eslintrc | 2 +
config/env/production.js | 2 +-
config/paths.js | 1 -
package.json | 51 +-
.../src/SecurityModule/core.js | 36 ++
.../src/SecurityModule/core.spec.js | 94 ++++
.../src/SecurityModule/index.js | 53 ++
.../src/network/api/http.js | 5 +-
.../src/network/api/index.js | 4 +-
.../src/network/walletApi.js | 13 +-
.../src/redux/kvStore/eth/sagaRegister.js | 4 +-
.../src/redux/kvStore/eth/sagas.js | 27 +-
.../src/redux/kvStore/root/sagas.js | 44 +-
.../src/redux/kvStore/sagaRegister.js | 22 +-
.../src/redux/kvStore/sagas.js | 24 +-
.../src/redux/kvStore/xlm/sagaRegister.js | 4 +-
.../src/redux/kvStore/xlm/sagas.js | 38 +-
.../src/redux/kvStore/xlm/sagas.spec.js | 93 ----
.../src/redux/payment/bch/sagas.js | 11 +-
.../src/redux/payment/btc/sagas.js | 8 +-
.../src/redux/payment/btc/sagas.spec.js | 9 +-
.../src/redux/payment/eth/sagas.js | 53 +-
.../src/redux/payment/sagas.js | 10 +-
.../src/redux/payment/xlm/sagas.js | 37 +-
.../src/redux/payment/xlm/sagas.spec.js | 3 -
.../src/redux/rootSaga.js | 12 +-
.../blockchain-wallet-v4/src/redux/sagas.js | 14 +-
.../src/redux/settings/sagaRegister.js | 4 +-
.../__snapshots__/walletReducers.spec.js.snap | 85 +++
.../src/redux/wallet/actionTypes.js | 2 +
.../src/redux/wallet/actions.js | 8 +
.../src/redux/wallet/reducers.js | 5 +
.../src/redux/wallet/sagaRegister.js | 7 +-
.../src/redux/wallet/sagas.js | 24 +-
.../src/redux/wallet/walletReducers.spec.js | 8 +
.../src/redux/walletSync/middleware.js | 9 +-
.../blockchain-wallet-v4/src/remote/index.js | 2 +
.../blockchain-wallet-v4/src/signer/bch.js | 5 +-
.../blockchain-wallet-v4/src/signer/bsv.js | 5 +-
.../blockchain-wallet-v4/src/signer/btc.js | 5 +-
.../blockchain-wallet-v4/src/signer/eth.js | 47 +-
.../blockchain-wallet-v4/src/signer/wifs.js | 11 +-
.../blockchain-wallet-v4/src/signer/xlm.js | 4 +-
.../src/types/HDWallet.js | 16 +-
.../src/types/HDWallet.spec.js | 43 +-
.../src/types/KVStoreEntry.js | 20 +-
.../src/types/Serializer.js | 17 +-
.../blockchain-wallet-v4/src/types/Wallet.js | 42 +-
.../src/types/Wallet.spec.js | 24 +-
.../blockchain-wallet-v4/src/types/Wrapper.js | 11 +
.../src/types/Wrapper.spec.js | 4 +
.../types/__snapshots__/Wallet.spec.js.snap | 87 +++
.../types/__snapshots__/Wrapper.spec.js.snap | 83 +++
.../blockchain-wallet-v4/src/utils/eth.js | 43 +-
.../src/utils/functional.js | 13 +-
.../blockchain-wallet-v4/src/utils/xlm.js | 15 +-
packages/main-process/babel.config.js | 32 +-
packages/main-process/package.json | 15 +-
packages/main-process/src/IPC/Middleware.js | 54 ++
packages/main-process/src/IPC/index.js | 28 +
.../__snapshots__/index.spec.js.snap | 1 +
.../main-process/src/data/analytics/sagas.js | 13 +-
.../main-process/src/data/auth/actionTypes.js | 1 +
.../main-process/src/data/auth/actions.js | 6 +
.../src/data/auth/sagaRegister.js | 5 +-
packages/main-process/src/data/auth/sagas.js | 19 +-
.../main-process/src/data/auth/sagas.spec.js | 47 +-
.../importBtcAddress/sagaRegister.js | 4 +-
.../src/data/components/sagaRegister.js | 46 +-
.../main-process/src/data/components/sagas.js | 4 +-
.../src/data/modules/sagaRegister.js | 16 +-
.../main-process/src/data/modules/sagas.js | 16 +-
.../src/data/modules/settings/sagaRegister.js | 5 +-
.../src/data/modules/settings/sagas.js | 53 +-
.../src/data/modules/settings/sagas.spec.js | 22 -
.../src/data/preferences/sagaRegister.js | 4 +-
.../src/data/preferences/sagas.js | 4 +-
packages/main-process/src/data/rootSaga.js | 77 +--
packages/main-process/src/index.dev.js | 17 +-
packages/main-process/src/index.html | 27 -
packages/main-process/src/index.prod.js | 12 +-
.../Wallet/Header/SecurityCenter/index.js | 12 +-
.../src/layouts/Wallet/Header/index.js | 3 +-
.../main-process/src/modals/Wallet/index.js | 3 +-
packages/main-process/src/modals/index.js | 3 +-
.../src/scenes/Settings/General/index.js | 20 -
packages/main-process/src/scenes/app.js | 39 +-
packages/main-process/src/store/index.js | 213 ++++----
packages/main-process/src/store/index.spec.js | 172 ------
packages/main-process/src/template.html | 12 +
packages/main-process/webpack.config.ci.js | 80 +++
packages/main-process/webpack.config.dev.js | 86 +++
packages/main-process/webpack.debug.js | 78 +++
packages/root-process/babel.config.js | 8 +
packages/root-process/package.json | 20 +
.../images/favicon/android-chrome-192x192.png | Bin 0 -> 4868 bytes
.../images/favicon/android-chrome-512x512.png | Bin 0 -> 13373 bytes
.../images/favicon/apple-touch-icon.png | Bin 0 -> 2421 bytes
.../assets/images/favicon/browserconfig.xml | 9 +
.../assets/images/favicon/favicon-16x16.png | Bin 0 -> 798 bytes
.../assets/images/favicon/favicon-32x32.png | Bin 0 -> 1079 bytes
.../src/assets/images/favicon/favicon.ico | Bin 0 -> 15086 bytes
.../assets/images/favicon/mstile-144x144.png | Bin 0 -> 2488 bytes
.../assets/images/favicon/mstile-150x150.png | Bin 0 -> 2502 bytes
.../assets/images/favicon/mstile-310x150.png | Bin 0 -> 2678 bytes
.../assets/images/favicon/mstile-310x310.png | Bin 0 -> 8106 bytes
.../assets/images/favicon/mstile-70x70.png | Bin 0 -> 1882 bytes
.../images/favicon/safari-pinned-tab.svg | 18 +
.../assets/images/favicon/site.webmanifest | 26 +
packages/root-process/src/favicons.js | 14 +
packages/root-process/src/index.js | 203 +++++++
packages/root-process/src/template.html | 62 +++
packages/root-process/webpack.config.ci.js | 49 ++
packages/root-process/webpack.config.dev.js | 48 ++
packages/root-process/webpack.debug.js | 48 ++
packages/security-process/babel.config.js | 32 +-
packages/security-process/package.json | 8 +-
.../security-process/src/IPC/Middleware.js | 71 +++
packages/security-process/src/IPC/index.js | 28 +
.../security-process/src/data/actionTypes.js | 6 -
packages/security-process/src/data/actions.js | 2 -
.../src/data/auth/actionTypes.js | 1 +
.../security-process/src/data/auth/actions.js | 6 +
.../src/data/auth/sagaRegister.js | 5 +-
.../security-process/src/data/auth/sagas.js | 55 +-
.../src/data/auth/sagas.spec.js | 268 +---------
.../src/data/components/actions.js | 64 +--
.../src/data/components/sagaRegister.js | 58 --
.../src/data/goals/sagas.spec.js | 35 --
packages/security-process/src/data/model.js | 4 +-
.../src/data/modules/actionTypes.js | 15 +-
.../src/data/modules/actions.js | 14 +-
.../src/data/modules/sagaRegister.js | 16 +-
.../src/data/modules/sagas.js | 16 +-
.../src/data/modules/selectors.js | 4 +-
.../src/data/modules/settings/actions.js | 5 -
.../src/data/modules/settings/sagaRegister.js | 8 +-
.../src/data/modules/settings/sagas.js | 83 +--
.../src/data/modules/settings/sagas.spec.js | 149 +++---
.../src/data/preferences/sagaRegister.js | 4 +-
.../src/data/preferences/sagas.js | 3 +-
.../security-process/src/data/rootReducer.js | 8 -
.../security-process/src/data/rootSaga.js | 38 +-
packages/security-process/src/data/sagas.js | 13 +-
.../security-process/src/data/selectors.js | 4 -
packages/security-process/src/index.dev.js | 22 +-
packages/security-process/src/index.html | 27 -
packages/security-process/src/index.prod.js | 16 +-
.../src/layouts/Security/Header/index.js | 63 +++
.../src/layouts/Security/index.js | 104 ++++
.../src/modals/Settings/index.js | 2 -
.../src/modals/Wallet/UpgradeWallet/index.js | 0
.../modals/Wallet/UpgradeWallet/template.js | 0
.../src/modals/Wallet/index.js | 3 +-
packages/security-process/src/modals/index.js | 132 +----
.../security-process/src/scenes/Home/index.js | 69 +--
.../AdvancedSecurity/PairingCode/index.js | 89 ++++
.../AdvancedSecurity/WalletId/index.js | 49 ++
.../SecurityCenter/AdvancedSecurity/index.js | 19 +
.../RecordBackupPhrase/FirstStep/index.js | 2 +
packages/security-process/src/scenes/app.js | 165 ++----
.../src/services/LocalesService/index.js | 9 +-
packages/security-process/src/store/index.js | 213 ++++----
packages/security-process/src/template.html | 12 +
.../security-process/webpack.config.ci.js | 80 +++
.../security-process/webpack.config.dev.js | 86 +++
packages/security-process/webpack.debug.js | 78 +++
packages/web-microkernel/babel.config.js | 12 +
packages/web-microkernel/package.json | 22 +
packages/web-microkernel/src/Core.js | 497 ++++++++++++++++++
packages/web-microkernel/src/Core.spec.js | 291 ++++++++++
packages/web-microkernel/src/index.js | 63 +++
packages/web-microkernel/src/mocks.js | 37 ++
packages/web-microkernel/wallaby.js | 13 +
packages/web-microkernel/yarn.lock | 8 +
webpack.config.ci.js | 138 ++---
webpack.config.dev.js | 405 ++++++--------
webpack.debug.js | 378 ++++++-------
yarn.lock | 339 +++++++++++-
179 files changed, 4686 insertions(+), 2857 deletions(-)
create mode 100644 packages/blockchain-wallet-v4/src/SecurityModule/core.js
create mode 100644 packages/blockchain-wallet-v4/src/SecurityModule/core.spec.js
create mode 100644 packages/blockchain-wallet-v4/src/SecurityModule/index.js
delete mode 100755 packages/blockchain-wallet-v4/src/redux/kvStore/xlm/sagas.spec.js
create mode 100644 packages/blockchain-wallet-v4/src/redux/wallet/__snapshots__/walletReducers.spec.js.snap
create mode 100644 packages/blockchain-wallet-v4/src/types/__snapshots__/Wallet.spec.js.snap
create mode 100644 packages/blockchain-wallet-v4/src/types/__snapshots__/Wrapper.spec.js.snap
create mode 100644 packages/main-process/src/IPC/Middleware.js
create mode 100644 packages/main-process/src/IPC/index.js
delete mode 100644 packages/main-process/src/index.html
delete mode 100644 packages/main-process/src/store/index.spec.js
create mode 100644 packages/main-process/src/template.html
create mode 100644 packages/main-process/webpack.config.ci.js
create mode 100644 packages/main-process/webpack.config.dev.js
create mode 100644 packages/main-process/webpack.debug.js
create mode 100644 packages/root-process/babel.config.js
create mode 100644 packages/root-process/package.json
create mode 100644 packages/root-process/src/assets/images/favicon/android-chrome-192x192.png
create mode 100644 packages/root-process/src/assets/images/favicon/android-chrome-512x512.png
create mode 100644 packages/root-process/src/assets/images/favicon/apple-touch-icon.png
create mode 100644 packages/root-process/src/assets/images/favicon/browserconfig.xml
create mode 100644 packages/root-process/src/assets/images/favicon/favicon-16x16.png
create mode 100644 packages/root-process/src/assets/images/favicon/favicon-32x32.png
create mode 100644 packages/root-process/src/assets/images/favicon/favicon.ico
create mode 100644 packages/root-process/src/assets/images/favicon/mstile-144x144.png
create mode 100644 packages/root-process/src/assets/images/favicon/mstile-150x150.png
create mode 100644 packages/root-process/src/assets/images/favicon/mstile-310x150.png
create mode 100644 packages/root-process/src/assets/images/favicon/mstile-310x310.png
create mode 100644 packages/root-process/src/assets/images/favicon/mstile-70x70.png
create mode 100644 packages/root-process/src/assets/images/favicon/safari-pinned-tab.svg
create mode 100644 packages/root-process/src/assets/images/favicon/site.webmanifest
create mode 100644 packages/root-process/src/favicons.js
create mode 100644 packages/root-process/src/index.js
create mode 100644 packages/root-process/src/template.html
create mode 100644 packages/root-process/webpack.config.ci.js
create mode 100644 packages/root-process/webpack.config.dev.js
create mode 100644 packages/root-process/webpack.debug.js
create mode 100644 packages/security-process/src/IPC/Middleware.js
create mode 100644 packages/security-process/src/IPC/index.js
delete mode 100644 packages/security-process/src/index.html
create mode 100644 packages/security-process/src/layouts/Security/Header/index.js
create mode 100644 packages/security-process/src/layouts/Security/index.js
rename packages/{main-process => security-process}/src/modals/Wallet/UpgradeWallet/index.js (100%)
rename packages/{main-process => security-process}/src/modals/Wallet/UpgradeWallet/template.js (100%)
create mode 100644 packages/security-process/src/scenes/SecurityCenter/AdvancedSecurity/PairingCode/index.js
create mode 100644 packages/security-process/src/scenes/SecurityCenter/AdvancedSecurity/WalletId/index.js
create mode 100644 packages/security-process/src/template.html
create mode 100644 packages/security-process/webpack.config.ci.js
create mode 100644 packages/security-process/webpack.config.dev.js
create mode 100644 packages/security-process/webpack.debug.js
create mode 100644 packages/web-microkernel/babel.config.js
create mode 100644 packages/web-microkernel/package.json
create mode 100644 packages/web-microkernel/src/Core.js
create mode 100644 packages/web-microkernel/src/Core.spec.js
create mode 100644 packages/web-microkernel/src/index.js
create mode 100644 packages/web-microkernel/src/mocks.js
create mode 100644 packages/web-microkernel/wallaby.js
create mode 100644 packages/web-microkernel/yarn.lock
diff --git a/.eslintrc b/.eslintrc
index 26e0bdeba83..33a125ba3b5 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -24,6 +24,8 @@
},
"rules": {
"camelcase": 0,
+ "no-undef": "error",
+ "no-unused-vars": "warn",
"generator-star-spacing": ["error", {"before": true, "after": true}],
"jest/no-disabled-tests": 1,
"jest/no-focused-tests": 2,
diff --git a/config/env/production.js b/config/env/production.js
index 9355f37df11..0e9e313c1f5 100644
--- a/config/env/production.js
+++ b/config/env/production.js
@@ -10,6 +10,6 @@ module.exports = {
ROOT_URL: 'https://blockchain.info',
THE_PIT_URL: 'https://pit.blockchain.com',
WEB_SOCKET_URL: 'wss://ws.blockchain.info',
- WALLET_HELPER_DOMAIN: 'https://wallet-helper.blockchain.info',
+ WALLET_HELPER_DOMAIN: 'https://wallet-helper.blockchain.com',
VERIFF_URL: 'https://magic.veriff.me'
}
diff --git a/config/paths.js b/config/paths.js
index 0ab3e0a8c74..3f748362742 100644
--- a/config/paths.js
+++ b/config/paths.js
@@ -6,7 +6,6 @@ const resolveRoot = relativePath => path.resolve(appDirectory, relativePath)
module.exports = {
appBuild: resolveRoot('lib'),
ciBuild: resolveRoot('dist'),
- src: resolveRoot('packages/blockchain-wallet-v4-frontend/src'),
pkgJson: resolveRoot('package.json'),
envConfig: resolveRoot('config/env'),
sslConfig: resolveRoot('config/ssl')
diff --git a/package.json b/package.json
index 8adbba9617d..ec158fcf515 100644
--- a/package.json
+++ b/package.json
@@ -57,24 +57,29 @@
]
},
"scripts": {
- "analyze": "yarn workspace blockchain-wallet-v4-frontend analyze",
- "build": "yarn workspace blockchain-wallet-v4-frontend build:dev",
- "build:prod": "yarn workspace blockchain-wallet-v4-frontend build:prod",
- "ci:compile": "yarn workspace blockchain-wallet-v4-frontend ci:compile",
+ "analyze": "cross-env-shell ANALYZE=true NODE_ENV=production webpack-cli --config webpack.config.ci.js",
+ "build:dev": "cross-env-shell NODE_ENV=development webpack-cli --config webpack.config.dev.js --progress --colors",
+ "build:prod": "cross-env-shell NODE_ENV=production webpack-cli --config webpack.config.dev.js --progress --colors",
+ "build:staging": "cross-env-shell NODE_ENV=staging webpack-cli --config webpack.config.dev.js --progress --colors",
+ "build:testnet": "cross-env-shell NODE_ENV=testnet webpack-cli --config webpack.config.dev.js --progress --colors",
+ "ci:compile": "cross-env-shell NODE_ENV=production webpack-cli --config webpack.config.ci.js --display-error-details",
"ci:coverage:components": "yarn workspace blockchain-info-components ci:coverage:components",
"ci:coverage:core": "yarn workspace blockchain-wallet-v4 ci:coverage:core",
- "ci:coverage:frontend": "yarn workspace blockchain-wallet-v4-frontend ci:coverage:frontend",
+ "ci:coverage:main-process": "yarn workspace main-process ci:coverage:frontend",
+ "ci:coverage:security-process": "yarn workspace security-process ci:coverage:frontend",
"ci:coverage:report": "istanbul report --root ./coverage --dir ./coverage/ lcov && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js",
"ci:lint": "prettier './packages/*/src/**/*.js' --loglevel error --write && eslint './packages/*/src/**/*.js' --fix && stylelint './packages/*/src/**/*.js'",
"ci:test:build": "yarn wsrun test:build --serial",
"ci:test:core:components": "yarn wsrun ci:test --serial --exclude-missing",
- "ci:test:frontend": "yarn workspace blockchain-wallet-v4-frontend ci:test:frontend",
+ "ci:test:main-process": "yarn workspace main-process ci:test:frontend",
+ "ci:test:security-process": "yarn workspace security-process ci:test:frontend",
"clean": "cross-env yarn wsrun clean && rimraf build && rimraf coverage && rimraf dist && rimraf *.log && rimraf node_modules",
"coverage": "cross-env rimraf coverage && yarn wsrun coverage --fast-exit && istanbul report --root ./coverage --dir ./coverage/ text-summary html",
"coverage:components": "yarn workspace blockchain-info-components coverage",
"coverage:core": "yarn workspace blockchain-wallet-v4 coverage",
- "coverage:frontend": "yarn workspace blockchain-wallet-v4-frontend coverage",
- "debug:prod": "yarn workspace blockchain-wallet-v4-frontend debug:prod",
+ "coverage:main-process": "yarn workspace main-process coverage",
+ "coverage:security-process": "yarn workspace security-process coverage",
+ "debug:prod": "cross-env-shell NODE_ENV=production webpack-dev-server --config webpack.debug.js --progress --colors",
"fix": "cross-env yarn prettier && yarn lint:fix && yarn test:components:update && yarn test:frontend:update",
"link:resolved:paths": "yarn wsrun link:resolved:paths --exclude-missing",
"lint": "eslint --cache './packages/*/src/**/*.js'",
@@ -82,17 +87,19 @@
"lint:core": "eslint './packages/blockchain-wallet-v4/src/**/*.js'",
"lint:css": "stylelint './packages/*/src/**/*.js'",
"lint:fix": "eslint './packages/*/src/**/*.js' --fix",
- "lint:frontend": "eslint './packages/blockchain-wallet-v4-frontend/src/**/*.js'",
- "manage:translations": "yarn workspace blockchain-wallet-v4-frontend manage:translations",
+ "lint:main-process": "eslint './packages/main-process/src/**/*.js'",
+ "lint:security-process": "eslint './packages/security-process/src/**/*.js'",
+ "manage:translations": "yarn build:prod && node ./translationRunner.js",
"prettier": "prettier './packages/*/src/**/*.js' --loglevel error --write",
"prettier:components": "prettier './packages/blockchain-info-components/src/**/*.js' --list-different --loglevel error --write",
"prettier:core": "prettier './packages/blockchain-wallet-v4/src/**/*.js' --list-different --loglevel error --write",
- "prettier:frontend": "prettier './packages/blockchain-wallet-v4-frontend/src/**/*.js' --list-different --loglevel error --write",
- "start": "yarn workspace blockchain-wallet-v4-frontend start:dev",
- "start:dev": "yarn workspace blockchain-wallet-v4-frontend start:dev",
- "start:prod": "yarn workspace blockchain-wallet-v4-frontend start:prod",
- "start:staging": "yarn workspace blockchain-wallet-v4-frontend start:staging",
- "start:testnet": "yarn workspace blockchain-wallet-v4-frontend start:testnet",
+ "prettier:main-process": "prettier './packages/main-process/src/**/*.js' --list-different --loglevel error --write",
+ "prettier:security-process": "prettier './packages/security-process/src/**/*.js' --list-different --loglevel error --write",
+ "start": "yarn start:dev",
+ "start:dev": "cross-env-shell NODE_ENV=development webpack-dev-server --config webpack.config.dev.js --progress --colors --watch --devtool cheap-module-source-map",
+ "start:prod": "cross-env-shell DISABLE_SSL=true NODE_ENV=production webpack-dev-server --config webpack.config.dev.js --progress --colors --watch --devtool cheap-module-source-map",
+ "start:staging": "cross-env-shell NODE_ENV=staging webpack-dev-server --config webpack.config.dev.js --progress --colors --watch --devtool cheap-module-source-map",
+ "start:testnet": "cross-env-shell NODE_ENV=testnet webpack-dev-server --config webpack.config.dev.js --progress --colors --watch --devtool cheap-module-source-map",
"storybook:build": "yarn workspace blockchain-info-components storybook:build",
"storybook:serve": "yarn workspace blockchain-info-components storybook:serve",
"storybook:deploy": "yarn workspace blockchain-info-components storybook:build && yarn workspace blockchain-info-components storybook:deploy",
@@ -104,10 +111,14 @@
"test:core": "yarn workspace blockchain-wallet-v4 test",
"test:core:debug": "yarn workspace blockchain-wallet-v4 test:debug",
"test:core:watch": "yarn workspace blockchain-wallet-v4 test:watch",
- "test:frontend": "yarn workspace blockchain-wallet-v4-frontend test",
- "test:frontend:debug": "yarn workspace blockchain-wallet-v4-frontend test:debug",
- "test:frontend:update": "yarn workspace blockchain-wallet-v4-frontend test:update",
- "test:frontend:watch": "yarn workspace blockchain-wallet-v4-frontend test:watch",
+ "test:main-process": "yarn workspace main-process test",
+ "test:main-process:debug": "yarn workspace main-process test:debug",
+ "test:main-process:update": "yarn workspace main-process test:update",
+ "test:main-process:watch": "yarn workspace main-process test:watch",
+ "test:security-process": "yarn workspace security-process test",
+ "test:security-process:debug": "yarn workspace security-process test:debug",
+ "test:security-process:update": "yarn workspace security-process test:update",
+ "test:security-process:watch": "yarn workspace security-process test:watch",
"release": "release-it"
},
"dependencies": {
diff --git a/packages/blockchain-wallet-v4/src/SecurityModule/core.js b/packages/blockchain-wallet-v4/src/SecurityModule/core.js
new file mode 100644
index 00000000000..25d1e714dda
--- /dev/null
+++ b/packages/blockchain-wallet-v4/src/SecurityModule/core.js
@@ -0,0 +1,36 @@
+export default ({ BIP39, Bitcoin, crypto, ed25519, EthHd }) => {
+ const credentialsEntropy = ({ guid, password, sharedKey }) =>
+ crypto.sha256(Buffer.from(guid + sharedKey + password))
+
+ const entropyToSeed = entropy =>
+ BIP39.mnemonicToSeed(BIP39.entropyToMnemonic(entropy))
+
+ const deriveBIP32KeyFromSeedHex = ({ entropy, network }, path) => {
+ const seed = entropyToSeed(entropy)
+
+ return Bitcoin.HDNode.fromSeedBuffer(seed, network)
+ .derivePath(path)
+ .toBase58()
+ }
+
+ // Derivation error using seedHex directly instead of seed derived from
+ // mnemonic derived from seedHex
+ const deriveLegacyEthereumKey = ({ entropy }) =>
+ EthHd.fromMasterSeed(entropy)
+ .derivePath(`m/44'/60'/0'/0/0`)
+ .getWallet()
+ .getPrivateKey()
+
+ const deriveSLIP10ed25519Key = async ({ entropy }, path) => {
+ const seed = entropyToSeed(entropy)
+ return ed25519.derivePath(path, seed.toString(`hex`))
+ }
+
+ return {
+ credentialsEntropy,
+ deriveBIP32KeyFromSeedHex,
+ deriveLegacyEthereumKey,
+ deriveSLIP10ed25519Key,
+ entropyToSeed
+ }
+}
diff --git a/packages/blockchain-wallet-v4/src/SecurityModule/core.spec.js b/packages/blockchain-wallet-v4/src/SecurityModule/core.spec.js
new file mode 100644
index 00000000000..7c89315bc4f
--- /dev/null
+++ b/packages/blockchain-wallet-v4/src/SecurityModule/core.spec.js
@@ -0,0 +1,94 @@
+import BIP39 from 'bip39'
+import Bitcoin from 'bitcoinjs-lib'
+import * as ed25519 from 'ed25519-hd-key'
+import EthHd from 'ethereumjs-wallet/hdkey'
+import * as StellarSdk from 'stellar-sdk'
+
+import Core from './core'
+import * as crypto from '../walletCrypto'
+import { taskToPromise } from '../utils/functional'
+
+const core = Core({ BIP39, Bitcoin, crypto, ed25519, EthHd, taskToPromise })
+
+it(`generates entropy from the user's credentials`, () => {
+ expect(
+ core
+ .credentialsEntropy({
+ guid: `50dae286-e42e-4d67-8419-d5dcc563746c`,
+ password: `password`,
+ sharedKey: `b91c904b-53ab-44b1-bf79-5b60c018da15`
+ })
+ .toString(`base64`)
+ ).toEqual(`jqdTiIA0jYETn9EjAGljE5697lc8kSkxod79srxfLug=`)
+})
+
+it(`entropyToSeed`, () => {
+ expect(
+ core.entropyToSeed(`713a3ae074e60e56c6bd0557c4984af1`).toString(`base64`)
+ ).toEqual(
+ `5KWmMucJQ65/B2Wd8TMhYJN/rYJYchakxkMVoPs5SX7koB923atMumgUeXfzoUe2rVhMQYCOgjigf2zEtYLxhg==`
+ )
+})
+
+it(`derives a BIP32 key from seedHex`, async () => {
+ expect(
+ await core.deriveBIP32KeyFromSeedHex(
+ {
+ network: Bitcoin.networks.bitcoin,
+ entropy: `713a3ae074e60e56c6bd0557c4984af1`
+ },
+ `m/0`
+ )
+ ).toEqual(
+ `xprv9vJpjafE9tbBCPBrcv5hBq1tUP4s4d3kZRHewAkGwzjvPZ3Jm8nt9eYwoLUcjnBKdB46WZmzuoEqWLJNB2GwyfShQ1y3Pn7AoVsGYXgzabG`
+ )
+})
+
+// Derivation error using seedHex directly instead of seed derived from
+// mnemonic derived from seedHex
+it(`derives a legacy Ethereum key from seedHex`, async () => {
+ expect(
+ (await core.deriveLegacyEthereumKey({
+ entropy: `e39c77ed95097f9006c34e1a843aa151`
+ })).toString(`hex`)
+ ).toEqual(`bb9c3e500b9c41ce9836619fb840436c2d98695d6dc43fb73e6e02df7ee7fc5c`)
+})
+
+describe(`derives a SLIP-10 ed25519 key from the seed`, () => {
+ const testVectors = [
+ {
+ seedHex: '713a3ae074e60e56c6bd0557c4984af1',
+ publicKey: 'GDRXE2BQUC3AZNPVFSCEZ76NJ3WWL25FYFK6RGZGIEKWE4SOOHSUJUJ6',
+ secret: 'SBGWSG6BTNCKCOB3DIFBGCVMUPQFYPA2G4O34RMTB343OYPXU5DJDVMN'
+ },
+ {
+ seedHex: 'b781c27351c7024355cf7f0b0efdc7f85e046cf9',
+ publicKey: 'GAVXVW5MCK7Q66RIBWZZKZEDQTRXWCZUP4DIIFXCCENGW2P6W4OA34RH',
+ secret: 'SAKS7I2PNDBE5SJSUSU2XLJ7K5XJ3V3K4UDFAHMSBQYPOKE247VHAGDB'
+ },
+ {
+ seedHex:
+ '150df9e3ab10f3f8f1428d723a6539662e181ec8781355396cec5fc2ce08d760',
+ publicKey: 'GC3MMSXBWHL6CPOAVERSJITX7BH76YU252WGLUOM5CJX3E7UCYZBTPJQ',
+ secret: 'SAEWIVK3VLNEJ3WEJRZXQGDAS5NVG2BYSYDFRSH4GKVTS5RXNVED5AX7'
+ },
+ {
+ seedHex: '00000000000000000000000000000000',
+ publicKey: 'GB3JDWCQJCWMJ3IILWIGDTQJJC5567PGVEVXSCVPEQOTDN64VJBDQBYX',
+ secret: 'SBUV3MRWKNS6AYKZ6E6MOUVF2OYMON3MIUASWL3JLY5E3ISDJFELYBRZ'
+ }
+ ]
+
+ testVectors.forEach(({ publicKey, secret, seedHex }, index) => {
+ it(`test vector ${index}`, async () => {
+ const { key } = await core.deriveSLIP10ed25519Key(
+ { entropy: Buffer.from(seedHex, `hex`) },
+ `m/44'/148'/0'`
+ )
+
+ const keypair = StellarSdk.Keypair.fromRawEd25519Seed(key)
+ expect(keypair.publicKey()).toEqual(publicKey)
+ expect(keypair.secret()).toEqual(secret)
+ })
+ })
+})
diff --git a/packages/blockchain-wallet-v4/src/SecurityModule/index.js b/packages/blockchain-wallet-v4/src/SecurityModule/index.js
new file mode 100644
index 00000000000..1007df6fab1
--- /dev/null
+++ b/packages/blockchain-wallet-v4/src/SecurityModule/index.js
@@ -0,0 +1,53 @@
+// Functions that require sensitive information to perform (e.g., password,
+// seed, and sharedKey). Think of this module as similar to a Hardware Security
+// Module.
+
+import BIP39 from 'bip39'
+import Bitcoin from 'bitcoinjs-lib'
+import * as ed25519 from 'ed25519-hd-key'
+
+import * as selectors from '../redux/wallet/selectors'
+import Core from './core'
+import * as types from '../types'
+import { taskToPromise } from '../utils/functional'
+import * as crypto from '../walletCrypto'
+
+const core = Core({ BIP39, Bitcoin, crypto, ed25519 })
+
+export default ({ store }) => {
+ const getSeedHex = ({ secondPassword }) => {
+ const state = store.getState()
+ const wallet = selectors.getWallet(state)
+ return taskToPromise(types.Wallet.getSeedHex(secondPassword, wallet))
+ }
+
+ const credentialsEntropy = ({ guid, sharedKey }) => {
+ const state = store.getState()
+ const password = selectors.getMainPassword(state)
+ return core.credentialsEntropy({ guid, password, sharedKey })
+ }
+
+ const deriveBIP32Key = async ({ network, secondPassword }, path) => {
+ const entropy = await getSeedHex({ secondPassword })
+ return core.deriveBIP32KeyFromSeedHex({ entropy, network }, path)
+ }
+
+ // Derivation error using seedHex directly instead of seed derived from
+ // mnemonic derived from seedHex
+ const deriveLegacyEthereumKey = async ({ secondPassword }) => {
+ const entropy = await getSeedHex({ secondPassword })
+ return core.deriveLegacyEthereumKey({ entropy })
+ }
+
+ const deriveSLIP10ed25519Key = async ({ secondPassword }, path) => {
+ const entropy = await getSeedHex({ secondPassword })
+ return core.deriveSLIP10ed25519Key({ entropy }, path)
+ }
+
+ return {
+ credentialsEntropy,
+ deriveBIP32Key,
+ deriveLegacyEthereumKey,
+ deriveSLIP10ed25519Key
+ }
+}
diff --git a/packages/blockchain-wallet-v4/src/network/api/http.js b/packages/blockchain-wallet-v4/src/network/api/http.js
index c9b5772c876..1e057fae36e 100755
--- a/packages/blockchain-wallet-v4/src/network/api/http.js
+++ b/packages/blockchain-wallet-v4/src/network/api/http.js
@@ -2,10 +2,12 @@ import axios from 'axios'
import queryString from 'query-string'
import { prop, path, pathOr, merge } from 'ramda'
+import * as kernel from 'web-microkernel/src'
+
axios.defaults.withCredentials = false
axios.defaults.timeout = Infinity
-export default ({ apiKey }) => {
+export default ({ apiKey, imports }) => {
const encodeData = (data, contentType) => {
const defaultData = {
api_code: apiKey,
@@ -39,6 +41,7 @@ export default ({ apiKey }) => {
...options
}) =>
axios({
+ adapter: kernel.sanitizeFunction(imports.axios),
url: `${url}${endPoint}`,
method,
data: encodeData(data, contentType),
diff --git a/packages/blockchain-wallet-v4/src/network/api/index.js b/packages/blockchain-wallet-v4/src/network/api/index.js
index 4abb22066f1..ce133c39966 100755
--- a/packages/blockchain-wallet-v4/src/network/api/index.js
+++ b/packages/blockchain-wallet-v4/src/network/api/index.js
@@ -16,17 +16,15 @@ import sfox from './sfox'
import trades from './trades'
import wallet from './wallet'
import xlm from './xlm'
-import httpService from './http'
import apiAuthorize from './apiAuthorize'
export default ({
+ http,
options,
- apiKey,
getAuthCredentials,
reauthenticate,
networks
} = {}) => {
- const http = httpService({ apiKey })
const authorizedHttp = apiAuthorize(http, getAuthCredentials, reauthenticate)
const apiUrl = options.domains.api
const coinifyUrl = options.domains.coinify
diff --git a/packages/blockchain-wallet-v4/src/network/walletApi.js b/packages/blockchain-wallet-v4/src/network/walletApi.js
index 731af279320..3c372744b9f 100755
--- a/packages/blockchain-wallet-v4/src/network/walletApi.js
+++ b/packages/blockchain-wallet-v4/src/network/walletApi.js
@@ -20,18 +20,9 @@ import { futurizeP } from 'futurize'
import createApi from './api'
import * as Coin from '../coinSelection/coin.js'
-const createWalletApi = (
- { options, apiKey, getAuthCredentials, reauthenticate, networks } = {},
- returnType
-) => {
+const createWalletApi = (options = {}, returnType) => {
// ////////////////////////////////////////////////////////////////
- const ApiPromise = createApi({
- options,
- apiKey,
- getAuthCredentials,
- reauthenticate,
- networks
- })
+ const ApiPromise = createApi(options)
const eitherToTask = e => e.fold(Task.rejected, Task.of)
const taskToPromise = t =>
new Promise((resolve, reject) => t.fork(reject, resolve))
diff --git a/packages/blockchain-wallet-v4/src/redux/kvStore/eth/sagaRegister.js b/packages/blockchain-wallet-v4/src/redux/kvStore/eth/sagaRegister.js
index 5ec889929df..a80a9eb12e3 100755
--- a/packages/blockchain-wallet-v4/src/redux/kvStore/eth/sagaRegister.js
+++ b/packages/blockchain-wallet-v4/src/redux/kvStore/eth/sagaRegister.js
@@ -2,8 +2,8 @@ import { takeLatest } from 'redux-saga/effects'
import * as AT from './actionTypes'
import sagas from './sagas'
-export default ({ api, networks }) => {
- const kvStoreEthSagas = sagas({ api, networks })
+export default (...args) => {
+ const kvStoreEthSagas = sagas(...args)
return function * coreKvStoreEthSaga () {
yield takeLatest(AT.FETCH_METADATA_ETH, kvStoreEthSagas.fetchMetadataEth)
diff --git a/packages/blockchain-wallet-v4/src/redux/kvStore/eth/sagas.js b/packages/blockchain-wallet-v4/src/redux/kvStore/eth/sagas.js
index 9b61cc7768d..0c4c53599dd 100755
--- a/packages/blockchain-wallet-v4/src/redux/kvStore/eth/sagas.js
+++ b/packages/blockchain-wallet-v4/src/redux/kvStore/eth/sagas.js
@@ -1,5 +1,6 @@
import {
assoc,
+ curry,
includes,
filter,
forEach,
@@ -20,28 +21,24 @@ import { KVStoreEntry } from '../../../types'
import { getMetadataXpriv } from '../root/selectors'
import { derivationMap, ETH } from '../config'
import * as eth from '../../../utils/eth'
-import { getMnemonic } from '../../wallet/selectors'
import {
getErc20CoinList,
getSupportedCoins
} from '../../walletOptions/selectors'
import { callTask } from '../../../utils/functional'
-export default ({ api, networks } = {}) => {
- const deriveAccount = function * (password) {
- try {
- const obtainMnemonic = state => getMnemonic(state, password)
- const mnemonicT = yield select(obtainMnemonic)
- const mnemonic = yield callTask(mnemonicT)
- const defaultIndex = 0
- const addr = eth.deriveAddress(mnemonic, defaultIndex)
+export default ({ api, networks, securityModule } = {}) => {
+ const deriveAccount = function * (secondPassword) {
+ const defaultIndex = 0
- return { defaultIndex, addr }
- } catch (e) {
- throw new Error(
- '[NOT IMPLEMENTED] MISSING_SECOND_PASSWORD in core.createEth saga'
- )
- }
+ const addr = yield call(
+ eth.deriveAddress,
+ securityModule,
+ secondPassword,
+ defaultIndex
+ )
+
+ return { defaultIndex, addr }
}
const buildErc20Entry = (token, coinModels) => ({
label: `My ${coinModels[token].displayName} Wallet`,
diff --git a/packages/blockchain-wallet-v4/src/redux/kvStore/root/sagas.js b/packages/blockchain-wallet-v4/src/redux/kvStore/root/sagas.js
index 8d82d26239d..9ffdd1c7c5f 100755
--- a/packages/blockchain-wallet-v4/src/redux/kvStore/root/sagas.js
+++ b/packages/blockchain-wallet-v4/src/redux/kvStore/root/sagas.js
@@ -1,18 +1,13 @@
import { call, put, select } from 'redux-saga/effects'
import { prop, compose, isNil } from 'ramda'
import * as A from './actions'
-import BIP39 from 'bip39'
import { KVStoreEntry } from '../../../types'
-import {
- getMnemonic,
- getGuid,
- getMainPassword,
- getSharedKey
-} from '../../wallet/selectors'
+import { getGuid, getSharedKey } from '../../wallet/selectors'
+
const taskToPromise = t =>
new Promise((resolve, reject) => t.fork(reject, resolve))
-export default ({ api, networks }) => {
+export default ({ api, securityModule = {}, networks }) => {
const callTask = function * (task) {
return yield call(
compose(
@@ -23,16 +18,15 @@ export default ({ api, networks }) => {
}
const createRoot = function * ({ password }) {
try {
- const obtainMnemonic = state => getMnemonic(state, password)
- const mnemonicT = yield select(obtainMnemonic)
- const mnemonic = yield call(() => taskToPromise(mnemonicT))
- const seedHex = BIP39.mnemonicToEntropy(mnemonic)
- const getMetadataNode = compose(
- KVStoreEntry.deriveMetadataNode,
- KVStoreEntry.getMasterHDNode(networks.btc)
+ const metadata = yield call(
+ securityModule.deriveBIP32Key,
+ {
+ network: networks.btc,
+ secondPassword: password
+ },
+ `m/${KVStoreEntry.metadataPurpose}'`
)
- const metadataNode = getMetadataNode(seedHex)
- const metadata = metadataNode.toBase58()
+
yield put(A.updateMetadataRoot({ metadata }))
} catch (e) {
throw new Error('create root Metadata :: Error decrypting mnemonic')
@@ -43,14 +37,18 @@ export default ({ api, networks }) => {
try {
const guid = yield select(getGuid)
const sharedKey = yield select(getSharedKey)
- const mainPassword = yield select(getMainPassword)
yield put(A.fetchMetadataRootLoading())
- const kv = KVStoreEntry.fromCredentials(
+
+ const entropy = yield call(securityModule.credentialsEntropy, {
guid,
- sharedKey,
- mainPassword,
- networks.btc
- )
+ sharedKey
+ })
+
+ const kv = yield call(KVStoreEntry.fromEntropy, {
+ entropy,
+ network: networks.btc
+ })
+
const newkv = yield callTask(api.fetchKVStore(kv))
yield put(A.fetchMetadataRootSuccess(newkv))
if (isNil(prop('metadata', newkv.value))) {
diff --git a/packages/blockchain-wallet-v4/src/redux/kvStore/sagaRegister.js b/packages/blockchain-wallet-v4/src/redux/kvStore/sagaRegister.js
index 936519aa3fc..bb048e4c70b 100755
--- a/packages/blockchain-wallet-v4/src/redux/kvStore/sagaRegister.js
+++ b/packages/blockchain-wallet-v4/src/redux/kvStore/sagaRegister.js
@@ -11,16 +11,16 @@ import contacts from './contacts/sagaRegister'
import lockbox from './lockbox/sagaRegister'
import userCredentials from './userCredentials/sagaRegister'
-export default ({ api, networks }) =>
+export default (...args) =>
function * coreKvStoreSaga () {
- yield fork(whatsNew({ api, networks }))
- yield fork(eth({ api, networks }))
- yield fork(bch({ api, networks }))
- yield fork(btc({ api, networks }))
- yield fork(xlm({ api, networks }))
- yield fork(shapeShift({ api, networks }))
- yield fork(buySell({ api, networks }))
- yield fork(contacts({ api, networks }))
- yield fork(lockbox({ api, networks }))
- yield fork(userCredentials({ api, networks }))
+ yield fork(whatsNew(...args))
+ yield fork(eth(...args))
+ yield fork(bch(...args))
+ yield fork(btc(...args))
+ yield fork(xlm(...args))
+ yield fork(shapeShift(...args))
+ yield fork(buySell(...args))
+ yield fork(contacts(...args))
+ yield fork(lockbox(...args))
+ yield fork(userCredentials(...args))
}
diff --git a/packages/blockchain-wallet-v4/src/redux/kvStore/sagas.js b/packages/blockchain-wallet-v4/src/redux/kvStore/sagas.js
index 5114ebff65e..832cdf3f55b 100755
--- a/packages/blockchain-wallet-v4/src/redux/kvStore/sagas.js
+++ b/packages/blockchain-wallet-v4/src/redux/kvStore/sagas.js
@@ -10,16 +10,16 @@ import userCredentials from './userCredentials/sagas'
import shapeShift from './shapeShift/sagas'
import xlm from './xlm/sagas'
-export default ({ api, networks }) => ({
- bch: bch({ api, networks }),
- btc: btc({ api, networks }),
- eth: eth({ api, networks }),
- root: root({ api, networks }),
- lockbox: lockbox({ api, networks }),
- buySell: buySell({ api, networks }),
- whatsNew: whatsNew({ api, networks }),
- contacts: contacts({ api, networks }),
- shapeShift: shapeShift({ api, networks }),
- userCredentials: userCredentials({ api, networks }),
- xlm: xlm({ api, networks })
+export default (...args) => ({
+ bch: bch(...args),
+ btc: btc(...args),
+ eth: eth(...args),
+ root: root(...args),
+ lockbox: lockbox(...args),
+ buySell: buySell(...args),
+ whatsNew: whatsNew(...args),
+ contacts: contacts(...args),
+ shapeShift: shapeShift(...args),
+ userCredentials: userCredentials(...args),
+ xlm: xlm(...args)
})
diff --git a/packages/blockchain-wallet-v4/src/redux/kvStore/xlm/sagaRegister.js b/packages/blockchain-wallet-v4/src/redux/kvStore/xlm/sagaRegister.js
index 194aa8e2694..d23831ae065 100755
--- a/packages/blockchain-wallet-v4/src/redux/kvStore/xlm/sagaRegister.js
+++ b/packages/blockchain-wallet-v4/src/redux/kvStore/xlm/sagaRegister.js
@@ -2,8 +2,8 @@ import { takeLatest } from 'redux-saga/effects'
import * as AT from './actionTypes'
import sagas from './sagas'
-export default ({ api, networks }) => {
- const kvStoreXlmSagas = sagas({ api, networks })
+export default (...args) => {
+ const kvStoreXlmSagas = sagas(...args)
return function * coreKvStoreXlmSaga () {
yield takeLatest(AT.FETCH_METADATA_XLM, kvStoreXlmSagas.fetchMetadataXlm)
diff --git a/packages/blockchain-wallet-v4/src/redux/kvStore/xlm/sagas.js b/packages/blockchain-wallet-v4/src/redux/kvStore/xlm/sagas.js
index 836dce856c4..be78e9c25e8 100755
--- a/packages/blockchain-wallet-v4/src/redux/kvStore/xlm/sagas.js
+++ b/packages/blockchain-wallet-v4/src/redux/kvStore/xlm/sagas.js
@@ -5,34 +5,26 @@ import * as A from './actions'
import { KVStoreEntry } from '../../../types'
import { getMetadataXpriv } from '../root/selectors'
import { derivationMap, XLM } from '../config'
-import { getMnemonic } from '../../wallet/selectors'
import { getKeyPair } from '../../../utils/xlm'
import { callTask } from '../../../utils/functional'
-export default ({ api, networks } = {}) => {
+export default ({ api, networks, securityModule } = {}) => {
const createXlm = function * ({ kv, password }) {
- try {
- const mnemonicT = yield select(getMnemonic, password)
- const mnemonic = yield callTask(mnemonicT)
- const keypair = getKeyPair(mnemonic)
- const xlm = {
- default_account_idx: 0,
- accounts: [
- {
- publicKey: keypair.publicKey(),
- label: 'My Stellar Wallet',
- archived: false
- }
- ],
- tx_notes: {}
- }
- const newkv = set(KVStoreEntry.value, xlm, kv)
- yield put(A.createMetadataXlm(newkv))
- } catch (e) {
- throw new Error(
- '[NOT IMPLEMENTED] MISSING_SECOND_PASSWORD in core.createXlm saga'
- )
+ const keypair = yield call(getKeyPair, securityModule, password)
+
+ const xlm = {
+ default_account_idx: 0,
+ accounts: [
+ {
+ publicKey: keypair.publicKey(),
+ label: 'My Stellar Wallet',
+ archived: false
+ }
+ ],
+ tx_notes: {}
}
+ const newkv = set(KVStoreEntry.value, xlm, kv)
+ yield put(A.createMetadataXlm(newkv))
}
const fetchMetadataXlm = function * (secondPasswordSagaEnhancer) {
diff --git a/packages/blockchain-wallet-v4/src/redux/kvStore/xlm/sagas.spec.js b/packages/blockchain-wallet-v4/src/redux/kvStore/xlm/sagas.spec.js
deleted file mode 100755
index e4ad69da46b..00000000000
--- a/packages/blockchain-wallet-v4/src/redux/kvStore/xlm/sagas.spec.js
+++ /dev/null
@@ -1,93 +0,0 @@
-import { select } from 'redux-saga/effects'
-import { expectSaga } from 'redux-saga-test-plan'
-import Task from 'data.task'
-import BIP39 from 'bip39'
-import * as ed25519 from 'ed25519-hd-key'
-import { set } from 'ramda-lens'
-
-import { getMnemonic } from '../../wallet/selectors'
-import { KVStoreEntry } from '../../../types'
-import { derivationMap, XLM } from '../config'
-import * as A from './actions'
-import sagas from './sagas'
-
-jest.spyOn(BIP39, 'mnemonicToSeed')
-jest.spyOn(ed25519, 'derivePath')
-
-const TEST_DATA = [
- {
- mnemonic:
- 'illness spike retreat truth genius clock brain pass fit cave bargain toe',
- seedHex:
- 'e4a5a632e70943ae7f07659df1332160937fad82587216a4c64315a0fb39497ee4a01f76ddab4cba68147977f3a147b6ad584c41808e8238a07f6cc4b582f186',
- publicKey: 'GDRXE2BQUC3AZNPVFSCEZ76NJ3WWL25FYFK6RGZGIEKWE4SOOHSUJUJ6',
- secret: 'SBGWSG6BTNCKCOB3DIFBGCVMUPQFYPA2G4O34RMTB343OYPXU5DJDVMN'
- },
- {
- mnemonic:
- 'resource asthma orphan phone ice canvas fire useful arch jewel impose vague theory cushion top',
- seedHex:
- '7b36d4e725b48695c3ffd2b4b317d5552cb157c1a26c46d36a05317f0d3053eb8b3b6496ba39ebd9312d10e3f9937b47a6790541e7c577da027a564862e92811',
- publicKey: 'GAVXVW5MCK7Q66RIBWZZKZEDQTRXWCZUP4DIIFXCCENGW2P6W4OA34RH',
- secret: 'SAKS7I2PNDBE5SJSUSU2XLJ7K5XJ3V3K4UDFAHMSBQYPOKE247VHAGDB'
- },
- {
- mnemonic:
- 'bench hurt jump file august wise shallow faculty impulse spring exact slush thunder author capable act festival slice deposit sauce coconut afford frown better',
- seedHex:
- '937ae91f6ab6f12461d9936dfc1375ea5312d097f3f1eb6fed6a82fbe38c85824da8704389831482db0433e5f6c6c9700ff1946aa75ad8cc2654d6e40f567866',
- publicKey: 'GC3MMSXBWHL6CPOAVERSJITX7BH76YU252WGLUOM5CJX3E7UCYZBTPJQ',
- secret: 'SAEWIVK3VLNEJ3WEJRZXQGDAS5NVG2BYSYDFRSH4GKVTS5RXNVED5AX7'
- },
- {
- mnemonic:
- 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about',
- seedHex:
- '5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc19a5ac40b389cd370d086206dec8aa6c43daea6690f20ad3d8d48b2d2ce9e38e4',
- publicKey: 'GB3JDWCQJCWMJ3IILWIGDTQJJC5567PGVEVXSCVPEQOTDN64VJBDQBYX',
- secret: 'SBUV3MRWKNS6AYKZ6E6MOUVF2OYMON3MIUASWL3JLY5E3ISDJFELYBRZ'
- }
-]
-
-const api = {}
-const { createXlm } = sagas({ api })
-
-const password = 'pAssword1<>!'
-const kv = KVStoreEntry.createEmpty(derivationMap[XLM])
-
-describe('Create XLM', () => {
- beforeEach(() => {
- BIP39.mnemonicToSeed.mockClear()
- ed25519.derivePath.mockClear()
- })
- TEST_DATA.forEach((testData, index) => {
- it(`Test data ${index}: should select mnemonic`, () => {
- const xlm = {
- default_account_idx: 0,
- accounts: [
- {
- publicKey: testData.publicKey,
- label: 'My Stellar Wallet',
- archived: false
- }
- ],
- tx_notes: {}
- }
- const newkv = set(KVStoreEntry.value, xlm, kv)
-
- return expectSaga(createXlm, { kv, password })
- .provide([[select(getMnemonic, password), Task.of(testData.mnemonic)]])
- .put(A.createMetadataXlm(newkv))
- .run()
- .then(() => {
- expect(BIP39.mnemonicToSeed).toHaveBeenCalledTimes(1)
- expect(BIP39.mnemonicToSeed).toHaveBeenCalledWith(testData.mnemonic)
- expect(ed25519.derivePath).toHaveBeenCalledTimes(1)
- expect(ed25519.derivePath).toHaveBeenCalledWith(
- "m/44'/148'/0'",
- testData.seedHex
- )
- })
- })
- })
-})
diff --git a/packages/blockchain-wallet-v4/src/redux/payment/bch/sagas.js b/packages/blockchain-wallet-v4/src/redux/payment/bch/sagas.js
index da154051f5c..dd79fc3da88 100755
--- a/packages/blockchain-wallet-v4/src/redux/payment/bch/sagas.js
+++ b/packages/blockchain-wallet-v4/src/redux/payment/bch/sagas.js
@@ -46,7 +46,7 @@ const fallbackFees = { priority: 4, regular: 4 }
.chain().fee(myFee).amount(myAmount).done()
*/
-export default ({ api }) => {
+export default ({ api, securityModule }) => {
// ///////////////////////////////////////////////////////////////////////////
const settingsSagas = settingsSagaFactory({ api })
const pushBchTx = futurizeP(Task)(api.pushBchTx)
@@ -249,7 +249,14 @@ export default ({ api }) => {
case ADDRESS_TYPES.ACCOUNT:
return yield call(() =>
taskToPromise(
- bch.signHDWallet(network, password, wrapper, selection, coinDust)
+ bch.signHDWallet(
+ securityModule,
+ network,
+ password,
+ wrapper,
+ selection,
+ coinDust
+ )
)
)
case ADDRESS_TYPES.LEGACY:
diff --git a/packages/blockchain-wallet-v4/src/redux/payment/btc/sagas.js b/packages/blockchain-wallet-v4/src/redux/payment/btc/sagas.js
index 02d91f0797a..9c0ed9126ab 100755
--- a/packages/blockchain-wallet-v4/src/redux/payment/btc/sagas.js
+++ b/packages/blockchain-wallet-v4/src/redux/payment/btc/sagas.js
@@ -44,7 +44,7 @@ export const taskToPromise = t =>
.chain().fee(myFee).amount(myAmount).done()
*/
-export default ({ api }) => {
+export default ({ api, securityModule }) => {
const settingsSagas = settingsSagaFactory({ api })
const __pushBtcTx = futurizeP(Task)(api.pushBtcTx)
const __getWalletUnspent = (network, fromData) =>
@@ -218,6 +218,7 @@ export default ({ api }) => {
}
const __calculateSignature = function * (
+ securityModule,
network,
password,
transport,
@@ -233,7 +234,9 @@ export default ({ api }) => {
switch (fromType) {
case ADDRESS_TYPES.ACCOUNT:
return yield call(() =>
- taskToPromise(btc.signHDWallet(network, password, wrapper, selection))
+ taskToPromise(
+ btc.signHDWallet(securityModule, network, password, wrapper, selection)
+ )
)
case ADDRESS_TYPES.LEGACY:
return yield call(() =>
@@ -326,6 +329,7 @@ export default ({ api }) => {
* sign (password, transport, scrambleKey) {
let signed = yield call(
__calculateSignature,
+ securityModule,
network,
password,
transport,
diff --git a/packages/blockchain-wallet-v4/src/redux/payment/btc/sagas.spec.js b/packages/blockchain-wallet-v4/src/redux/payment/btc/sagas.spec.js
index 22c420b0c2a..2912df2e056 100755
--- a/packages/blockchain-wallet-v4/src/redux/payment/btc/sagas.spec.js
+++ b/packages/blockchain-wallet-v4/src/redux/payment/btc/sagas.spec.js
@@ -67,6 +67,8 @@ Coin.fromJS.mockImplementation(() => true)
let api = { getBtcFees: () => feeResult }
describe('createPayment', () => {
+ const securityModule = {}
+
let {
create,
__calculateTo,
@@ -79,7 +81,7 @@ describe('createPayment', () => {
__calculateSignature,
__calculateSweepSelection,
__getWalletUnspent
- } = createPaymentFactory({ api })
+ } = createPaymentFactory({ api, securityModule })
let payment = create({ network, payment: p })
describe('*init', () => {
@@ -159,6 +161,7 @@ describe('createPayment', () => {
expect(gen.next(PASSWORD_VALUE).value).toEqual(
call(
__calculateSignature,
+ securityModule,
network,
PASSWORD_VALUE,
TRANSPORT_VALUE,
@@ -187,6 +190,7 @@ describe('createPayment', () => {
it('should follow the ADDRESS_TYPES.ACCOUNT case', () => {
let WRAPPER_VALUE = {}
let result = __calculateSignature(
+ securityModule,
network,
PASSWORD_VALUE,
TRANSPORT_VALUE,
@@ -201,6 +205,7 @@ describe('createPayment', () => {
it('should follow the ADDRESS_TYPES.LEGACY case', () => {
let WRAPPER_VALUE = {}
let result = __calculateSignature(
+ securityModule,
network,
PASSWORD_VALUE,
TRANSPORT_VALUE,
@@ -214,6 +219,7 @@ describe('createPayment', () => {
})
it('should follow the ADDRESS_TYPES.EXTERNAL case', () => {
let result = __calculateSignature(
+ securityModule,
network,
PASSWORD_VALUE,
TRANSPORT_VALUE,
@@ -226,6 +232,7 @@ describe('createPayment', () => {
})
it('should follow the ADDRESS_TYPES.WATCH_ONLY case', () => {
let result = __calculateSignature(
+ securityModule,
network,
PASSWORD_VALUE,
TRANSPORT_VALUE,
diff --git a/packages/blockchain-wallet-v4/src/redux/payment/eth/sagas.js b/packages/blockchain-wallet-v4/src/redux/payment/eth/sagas.js
index 319575e85e5..b2edb6373ab 100755
--- a/packages/blockchain-wallet-v4/src/redux/payment/eth/sagas.js
+++ b/packages/blockchain-wallet-v4/src/redux/payment/eth/sagas.js
@@ -37,7 +37,7 @@ const taskToPromise = t =>
.chain().amount(myAmount).done()
*/
-export default ({ api }) => {
+export default ({ api, securityModule }) => {
const settingsSagas = settingsSagaFactory({ api })
const selectIndex = function * (from) {
const appState = yield select(identity)
@@ -60,8 +60,8 @@ export default ({ api }) => {
}
const calculateSignature = function * (
+ securityModule,
network,
- password,
transport,
scrambleKey,
p
@@ -69,9 +69,6 @@ export default ({ api }) => {
switch (p.raw.fromType) {
case ADDRESS_TYPES.ACCOUNT: {
let sign
- const appState = yield select(identity)
- const mnemonicT = S.wallet.getMnemonic(appState, password)
- const mnemonic = yield call(() => taskToPromise(mnemonicT))
if (p.isErc20) {
const contractAddress = (yield select(
S.kvStore.eth.getErc20ContractAddr,
@@ -79,10 +76,10 @@ export default ({ api }) => {
)).getOrFail('missing_contract_addr')
sign = data =>
taskToPromise(
- eth.signErc20(network, mnemonic, data, contractAddress)
+ eth.signErc20(network, securityModule, data, contractAddress)
)
} else {
- sign = data => taskToPromise(eth.sign(network, mnemonic, data))
+ sign = data => taskToPromise(eth.sign(network, securityModule, data))
}
return yield call(sign, p.raw)
}
@@ -276,34 +273,24 @@ export default ({ api }) => {
return makePayment(mergeRight(p, { raw }))
},
- * sign (password, transport, scrambleKey) {
- try {
- const signed = yield call(
- calculateSignature,
- network,
- password,
- transport,
- scrambleKey,
- p
- )
- return makePayment(mergeRight(p, { signed }))
- } catch (e) {
- throw new Error('missing_mnemonic')
- }
+ * sign (transport, scrambleKey) {
+ const signed = yield call(
+ calculateSignature,
+ network,
+ transport,
+ scrambleKey,
+ p
+ )
+ return makePayment(mergeRight(p, { signed }))
},
- * signLegacy (password) {
- try {
- const appState = yield select(identity)
- const seedHexT = S.wallet.getSeedHex(appState, password)
- const seedHex = yield call(() => taskToPromise(seedHexT))
- const signLegacy = data =>
- taskToPromise(eth.signLegacy(network, seedHex, data))
- const signed = yield call(signLegacy, p.raw)
- return makePayment(mergeRight(p, { signed }))
- } catch (e) {
- throw new Error('missing_seed_hex')
- }
+ * signLegacy (secondPassword) {
+ const signLegacy = data =>
+ taskToPromise(
+ eth.signLegacy(network, securityModule, secondPassword, data)
+ )
+ const signed = yield call(signLegacy, p.raw)
+ return makePayment(mergeRight(p, { signed }))
},
* publish () {
diff --git a/packages/blockchain-wallet-v4/src/redux/payment/sagas.js b/packages/blockchain-wallet-v4/src/redux/payment/sagas.js
index cc9c21be76b..968f95d3d56 100755
--- a/packages/blockchain-wallet-v4/src/redux/payment/sagas.js
+++ b/packages/blockchain-wallet-v4/src/redux/payment/sagas.js
@@ -3,9 +3,9 @@ import bch from './bch/sagas'
import eth from './eth/sagas'
import xlm from './xlm/sagas'
-export default ({ api }) => ({
- btc: btc({ api }),
- bch: bch({ api }),
- eth: eth({ api }),
- xlm: xlm({ api })
+export default (...args) => ({
+ btc: btc(...args),
+ bch: bch(...args),
+ eth: eth(...args),
+ xlm: xlm(...args)
})
diff --git a/packages/blockchain-wallet-v4/src/redux/payment/xlm/sagas.js b/packages/blockchain-wallet-v4/src/redux/payment/xlm/sagas.js
index 5278b94d566..789866fb4bd 100755
--- a/packages/blockchain-wallet-v4/src/redux/payment/xlm/sagas.js
+++ b/packages/blockchain-wallet-v4/src/redux/payment/xlm/sagas.js
@@ -54,7 +54,7 @@ export const NO_TX_ERROR = 'No transaction'
export const NO_SIGNED_ERROR = 'No signed tx'
export const WRONG_MEMO_FORMAT = 'Bad memo'
-export default ({ api }) => {
+export default ({ api, securityModule }) => {
const settingsSagas = settingsSagaFactory({ api })
// ///////////////////////////////////////////////////////////////////////////
const calculateTo = destination => {
@@ -66,7 +66,7 @@ export default ({ api }) => {
}
const calculateSignature = function * (
- password,
+ secondPassword,
transaction,
transport,
scrambleKey,
@@ -75,9 +75,12 @@ export default ({ api }) => {
switch (fromType) {
case ADDRESS_TYPES.ACCOUNT:
if (!transaction) throw new Error(NO_TX_ERROR)
- const mnemonicT = yield select(flip(S.wallet.getMnemonic)(password))
- const mnemonic = yield call(() => taskToPromise(mnemonicT))
- return xlmSigner.sign({ transaction }, mnemonic)
+
+ return yield call(
+ xlmSigner.sign,
+ { secondPassword, securityModule },
+ transaction
+ )
case ADDRESS_TYPES.LOCKBOX:
return yield call(
xlmSigner.signWithLockbox,
@@ -258,20 +261,16 @@ export default ({ api }) => {
},
* sign (password, transport, scrambleKey) {
- try {
- const transaction = prop('transaction', p)
- const signed = yield call(
- calculateSignature,
- password,
- transaction,
- transport,
- scrambleKey,
- path(['from', 'type'], p)
- )
- return makePayment(merge(p, { signed }))
- } catch (e) {
- throw new Error('missing_mnemonic')
- }
+ const transaction = prop('transaction', p)
+ const signed = yield call(
+ calculateSignature,
+ password,
+ transaction,
+ transport,
+ scrambleKey,
+ path(['from', 'type'], p)
+ )
+ return makePayment(merge(p, { signed }))
},
* publish () {
diff --git a/packages/blockchain-wallet-v4/src/redux/payment/xlm/sagas.spec.js b/packages/blockchain-wallet-v4/src/redux/payment/xlm/sagas.spec.js
index 72ffad22e72..aa8a466fd30 100755
--- a/packages/blockchain-wallet-v4/src/redux/payment/xlm/sagas.spec.js
+++ b/packages/blockchain-wallet-v4/src/redux/payment/xlm/sagas.spec.js
@@ -99,7 +99,6 @@ S.data.xlm.getAccount.mockImplementation(id => () => {
if (id === OTHER_ACCOUNT_ID) return Remote.of(STUB_OTHER_ACCOUNT)
return null
})
-S.wallet.getMnemonic.mockReturnValue(() => Task.of(STUB_MNEMONIC))
xlmSigner.sign.mockReturnValue(STUB_SIGNED_TX)
@@ -425,8 +424,6 @@ describe.skip('payment', () => {
payment = await expectSaga(payment.sign, STUB_PASSWORD)
.run()
.then(prop('returnValue'))
- expect(S.wallet.getMnemonic).toHaveBeenCalledTimes(1)
- expect(S.wallet.getMnemonic.mock.calls[0][1]).toBe(STUB_PASSWORD)
expect(xlmSigner.sign).toHaveBeenCalledTimes(1)
expect(xlmSigner.sign).toHaveBeenCalledWith(
{ transaction: STUB_TX },
diff --git a/packages/blockchain-wallet-v4/src/redux/rootSaga.js b/packages/blockchain-wallet-v4/src/redux/rootSaga.js
index 27f860e56c0..1dab0b1374c 100755
--- a/packages/blockchain-wallet-v4/src/redux/rootSaga.js
+++ b/packages/blockchain-wallet-v4/src/redux/rootSaga.js
@@ -5,13 +5,13 @@ import walletOptions from './walletOptions/sagaRegister'
import settings from './settings/sagaRegister'
import wallet from './wallet/sagaRegister'
-export default ({ api, networks, options }) =>
+export default (...args) =>
function * coreSaga () {
yield all([
- fork(data({ api, options, networks })),
- fork(kvStore({ api, networks })),
- fork(walletOptions({ api, options })),
- fork(settings({ api })),
- fork(wallet({ api, networks }))
+ fork(data(...args)),
+ fork(kvStore(...args)),
+ fork(walletOptions(...args)),
+ fork(settings(...args)),
+ fork(wallet(...args))
])
}
diff --git a/packages/blockchain-wallet-v4/src/redux/sagas.js b/packages/blockchain-wallet-v4/src/redux/sagas.js
index 23a574f8cc0..63a49a60e81 100755
--- a/packages/blockchain-wallet-v4/src/redux/sagas.js
+++ b/packages/blockchain-wallet-v4/src/redux/sagas.js
@@ -5,11 +5,11 @@ import walletOptions from './walletOptions/sagas'
import kvStore from './kvStore/sagas'
import payment from './payment/sagas'
-export default ({ api, networks, options }) => ({
- data: data({ api, options, networks }),
- settings: settings({ api }),
- wallet: wallet({ api, networks }),
- walletOptions: walletOptions({ api }),
- kvStore: kvStore({ api, networks }),
- payment: payment({ api, options })
+export default (...args) => ({
+ data: data(...args),
+ settings: settings(...args),
+ wallet: wallet(...args),
+ walletOptions: walletOptions(...args),
+ kvStore: kvStore(...args),
+ payment: payment(...args)
})
diff --git a/packages/blockchain-wallet-v4/src/redux/settings/sagaRegister.js b/packages/blockchain-wallet-v4/src/redux/settings/sagaRegister.js
index 2bdb0b3fe4d..b06fa28a531 100755
--- a/packages/blockchain-wallet-v4/src/redux/settings/sagaRegister.js
+++ b/packages/blockchain-wallet-v4/src/redux/settings/sagaRegister.js
@@ -2,8 +2,8 @@ import { takeLatest } from 'redux-saga/effects'
import * as AT from './actionTypes'
import sagas from './sagas'
-export default ({ api }) => {
- const settingsSagas = sagas({ api })
+export default (...args) => {
+ const settingsSagas = sagas(...args)
return function * coreSettingsSaga () {
yield takeLatest(AT.FETCH_SETTINGS, settingsSagas.fetchSettings)
diff --git a/packages/blockchain-wallet-v4/src/redux/wallet/__snapshots__/walletReducers.spec.js.snap b/packages/blockchain-wallet-v4/src/redux/wallet/__snapshots__/walletReducers.spec.js.snap
new file mode 100644
index 00000000000..381885be8ec
--- /dev/null
+++ b/packages/blockchain-wallet-v4/src/redux/wallet/__snapshots__/walletReducers.spec.js.snap
@@ -0,0 +1,85 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`reducers wallet should handle MERGE_WRAPPER 1`] = `
+Immutable.Map {
+ "sync_pubkeys": false,
+ "payload_checksum": "payload_checksum",
+ "storage_token": "storage_token",
+ "version": 3,
+ "language": "en",
+ "wallet": Immutable.Map {
+ "addresses": Immutable.Map {
+ "19XmKRY66VnUn5irHAafyoTfiwFuGLUxKF": Immutable.Map {
+ "addr": "19XmKRY66VnUn5irHAafyoTfiwFuGLUxKF",
+ "priv": "38W3vsxt246Lhawvz81y2HQVnsJrBUX87jSTUUnixPsE",
+ "tag": 0,
+ "label": "",
+ "created_time": 1492721419269,
+ "created_device_name": "javascript_web",
+ "created_device_version": "3.0",
+ },
+ "14mQxLtEagsS8gYsdWJbzthFFuPDqDgtxQ": Immutable.Map {
+ "addr": "14mQxLtEagsS8gYsdWJbzthFFuPDqDgtxQ",
+ "priv": "BpD2ZuJjZ8PJPpDX6ZmsKFsXHkL7XV3dt385zghMfF6C",
+ "tag": 0,
+ "label": "labeled_imported",
+ "created_time": 1492721432222,
+ "created_device_name": "javascript_web",
+ "created_device_version": "3.0",
+ },
+ "1JD73aGSdeqKUjJ4ntP4eCyUiuZ3ogJE1u": Immutable.Map {
+ "addr": "1JD73aGSdeqKUjJ4ntP4eCyUiuZ3ogJE1u",
+ "priv": null,
+ "tag": 0,
+ "label": "",
+ "created_time": 1492721461228,
+ "created_device_name": "javascript_web",
+ "created_device_version": "3.0",
+ },
+ },
+ "tx_notes": Immutable.Map {},
+ "guid": "new guid",
+ "metadataHDNode": "xprv9tygGQP8be7uzNm5Czuy41juTK9pUKnWyZtDxgbmSEcCYa9VdvvtSknEyiKitqqm2TMv14NjXPQ68XLwSdH6Scc5GwXoZ31yRZZysxhVGU7",
+ "tx_names": Immutable.List [],
+ "double_encryption": false,
+ "address_book": Immutable.Map {},
+ "hd_wallets": Immutable.List [
+ Immutable.Map {
+ "seedHex": "6a4d9524d413fdf69ca1b5664d1d6db0",
+ "passphrase": "",
+ "mnemonic_verified": false,
+ "default_account_idx": 0,
+ "accounts": Immutable.List [
+ Immutable.Map {
+ "label": "My Bitcoin Wallet",
+ "archived": false,
+ "xpriv": "xprv9yL1ousLjQQzGNBAYykaT8J3U626NV6zbLYkRv8rvUDpY4f1RnrvAXQneGXC9UNuNvGXX4j6oHBK5KiV2hKevRxY5ntis212oxjEL11ysuG",
+ "xpub": "xpub6CKNDRQEZmyHUrFdf1HapGEn27ramwpqxZUMEJYUUokoQrz9yLBAiKjGVWDuiCT39udj1r3whqQN89Tar5KrojH8oqSy7ytzJKW8gwmhwD3",
+ "address_labels": Immutable.Map {
+ "0": Immutable.Map {
+ "index": 0,
+ "label": "labeled_address",
+ },
+ },
+ "cache": Immutable.Map {
+ "receiveAccount": "xpub6F41z8MqNcJMvKQgAd5QE2QYo32cocYigWp1D8726ykMmaMqvtqLkvuL1NqGuUJvU3aWyJaV2J4V6sD7Pv59J3tYGZdYRSx8gU7EG8ZuPSY",
+ "changeAccount": "xpub6F41z8MqNcJMwmeUExdCv7UXvYBEgQB29SWq9jyxuZ7WefmSTWcwXB6NRAJkGCkB3L1Eu4ttzWnPVKZ6REissrQ4i6p8gTi9j5YwDLxmZ8p",
+ },
+ "index": 0,
+ },
+ ],
+ },
+ ],
+ "sharedKey": "8a260b2b-5257-4357-ac56-7a7efca323ea",
+ "options": Immutable.Map {
+ "pbkdf2_iterations": 5000,
+ "fee_per_kb": 10000,
+ "html5_notifications": false,
+ "logout_time": 600000,
+ },
+ },
+ "war_checksum": "war_checksum",
+ "password": "password",
+ "pbkdf2_iterations": 5000,
+}
+`;
diff --git a/packages/blockchain-wallet-v4/src/redux/wallet/actionTypes.js b/packages/blockchain-wallet-v4/src/redux/wallet/actionTypes.js
index 1e71c3b27bd..de157ae6deb 100755
--- a/packages/blockchain-wallet-v4/src/redux/wallet/actionTypes.js
+++ b/packages/blockchain-wallet-v4/src/redux/wallet/actionTypes.js
@@ -1,4 +1,6 @@
// setters
+export const MERGE_WRAPPER = '@CORE.MERGE_WRAPPER'
+export const NEW_HDACCOUNT = '@CORE.NEW_HDACCOUNT'
export const SET_WRAPPER = '@CORE.SET_WRAPPER'
export const REFRESH_WRAPPER = '@CORE.REFRESH_WRAPPER'
export const SET_MAIN_PASSWORD = '@CORE.SET_MAIN_PASSWORD'
diff --git a/packages/blockchain-wallet-v4/src/redux/wallet/actions.js b/packages/blockchain-wallet-v4/src/redux/wallet/actions.js
index 624d77e3563..24afa32a1fa 100755
--- a/packages/blockchain-wallet-v4/src/redux/wallet/actions.js
+++ b/packages/blockchain-wallet-v4/src/redux/wallet/actions.js
@@ -1,6 +1,14 @@
import * as T from './actionTypes'
// setters
+
+export const mergeWrapper = payload => ({ type: T.MERGE_WRAPPER, payload })
+
+export const newHdAccount = payload => ({
+ type: T.NEW_HDACCOUNT,
+ payload
+})
+
export const setWrapper = payload => ({
type: T.SET_WRAPPER,
payload: payload
diff --git a/packages/blockchain-wallet-v4/src/redux/wallet/reducers.js b/packages/blockchain-wallet-v4/src/redux/wallet/reducers.js
index e432a1e65e3..358b0926555 100755
--- a/packages/blockchain-wallet-v4/src/redux/wallet/reducers.js
+++ b/packages/blockchain-wallet-v4/src/redux/wallet/reducers.js
@@ -11,6 +11,11 @@ export const WRAPPER_INITIAL_STATE = Wrapper.fromJS(
export const wrapperReducer = (state = WRAPPER_INITIAL_STATE, action) => {
const { type } = action
switch (type) {
+ // Merge wrapper from the Main Process.
+ case T.MERGE_WRAPPER: {
+ const redactedWrapper = Wrapper.redact(action.payload)
+ return state.mergeDeep(redactedWrapper)
+ }
case T.SET_PAYLOAD_CHECKSUM: {
const checksum = action.payload
return set(Wrapper.payloadChecksum, checksum, state)
diff --git a/packages/blockchain-wallet-v4/src/redux/wallet/sagaRegister.js b/packages/blockchain-wallet-v4/src/redux/wallet/sagaRegister.js
index 876401fb00d..35a86e7e0da 100755
--- a/packages/blockchain-wallet-v4/src/redux/wallet/sagaRegister.js
+++ b/packages/blockchain-wallet-v4/src/redux/wallet/sagaRegister.js
@@ -1,11 +1,12 @@
-import { takeLatest } from 'redux-saga/effects'
+import { takeEvery, takeLatest } from 'redux-saga/effects'
import * as AT from './actionTypes'
import sagas from './sagas'
-export default ({ api, networks }) => {
- const walletSagas = sagas({ api, networks })
+export default (...args) => {
+ const walletSagas = sagas(...args)
return function * coreWalletSaga () {
+ yield takeEvery(AT.NEW_HDACCOUNT, walletSagas.newHDAccount)
yield takeLatest(AT.SET_DEFAULT_ACCOUNT, walletSagas.refetchContextData)
yield takeLatest(AT.SET_ADDRESS_ARCHIVED, walletSagas.refetchContextData)
yield takeLatest(AT.SET_ACCOUNT_ARCHIVED, walletSagas.refetchContextData)
diff --git a/packages/blockchain-wallet-v4/src/redux/wallet/sagas.js b/packages/blockchain-wallet-v4/src/redux/wallet/sagas.js
index 786ea5c1459..9a449b42e46 100755
--- a/packages/blockchain-wallet-v4/src/redux/wallet/sagas.js
+++ b/packages/blockchain-wallet-v4/src/redux/wallet/sagas.js
@@ -30,7 +30,7 @@ import { generateMnemonic } from '../../walletCrypto'
const taskToPromise = t =>
new Promise((resolve, reject) => t.fork(reject, resolve))
-export default ({ api, networks }) => {
+export default ({ api, networks, securityModule }) => {
const runTask = function * (task, setActionCreator) {
let result = yield call(
compose(
@@ -77,10 +77,22 @@ export default ({ api, networks }) => {
}
const newHDAccount = function * ({ label, password }) {
+ const hdWallet = yield select(S.getDefaultHDWallet)
+ const index = hdWallet.accounts.size
+
+ const key = yield call(
+ securityModule.deriveBIP32Key,
+ {
+ network: networks.btc,
+ secondPassword: password
+ },
+ `m/44'/0'/${index}'`
+ )
+
let wrapper = yield select(S.getWrapper)
let nextWrapper = Wrapper.traverseWallet(
Task.of,
- Wallet.newHDAccount(label, password, networks.btc),
+ Wallet.newHDAccount(index, key, label, password),
wrapper
)
yield call(runTask, nextWrapper, A.wallet.setWrapper)
@@ -132,14 +144,10 @@ export default ({ api, networks }) => {
if (isEmpty(hdwallets)) {
let mnemonic = yield call(generateMnemonic, api)
- let upgradeWallet = Wallet.upgradeToHd(
- mnemonic,
- 'My Bitcoin Wallet',
- password,
- networks.btc
- )
+ const upgradeWallet = Wallet.newHDWallet(mnemonic, password)
let nextWrapper = Wrapper.traverseWallet(Task.of, upgradeWallet, wrapper)
yield call(runTask, nextWrapper, A.wallet.setWrapper)
+ yield put(A.wallet.newHdAccount({ label: `My Bitcoin Wallet`, password }))
} else {
throw new Error('Already an HD wallet')
}
diff --git a/packages/blockchain-wallet-v4/src/redux/wallet/walletReducers.spec.js b/packages/blockchain-wallet-v4/src/redux/wallet/walletReducers.spec.js
index 18c048cfe29..fc520a947dd 100755
--- a/packages/blockchain-wallet-v4/src/redux/wallet/walletReducers.spec.js
+++ b/packages/blockchain-wallet-v4/src/redux/wallet/walletReducers.spec.js
@@ -1,3 +1,4 @@
+import { fromJS } from 'immutable'
import { compose } from 'ramda'
import { Wrapper, Wallet, AddressMap } from '../../types'
import walletReducer from './reducers.js'
@@ -20,6 +21,13 @@ describe('reducers', () => {
describe('wallet', () => {
const wrapped = Wrapper.fromJS(wrap(walletFixture))
+ it('should handle MERGE_WRAPPER', () => {
+ const payload = fromJS({ wallet: { guid: `new guid` } })
+ const action = Actions.mergeWrapper(payload)
+ const next = walletReducer(wrapped, action)
+ expect(next).toMatchSnapshot()
+ })
+
it('should handle SET_WRAPPER', () => {
let action = Actions.setWrapper(wrapped)
let next = walletReducer(void 0, action)
diff --git a/packages/blockchain-wallet-v4/src/redux/walletSync/middleware.js b/packages/blockchain-wallet-v4/src/redux/walletSync/middleware.js
index e4cd9215e80..703b4d3cad9 100755
--- a/packages/blockchain-wallet-v4/src/redux/walletSync/middleware.js
+++ b/packages/blockchain-wallet-v4/src/redux/walletSync/middleware.js
@@ -101,7 +101,8 @@ export const getWalletAddresses = async (state, api) => {
*/
const walletSync = ({
isAuthenticated,
- api
+ api,
+ mergeWrapper = false
} = {}) => store => next => action => {
const prevState = store.getState()
const prevWallet = selectors.wallet.getWrapper(prevState)
@@ -164,7 +165,11 @@ const walletSync = ({
action.type !== T.wallet.SET_PAYLOAD_CHECKSUM &&
action.type !== T.wallet.REFRESH_WRAPPER &&
prevWallet !== nextWallet:
- sync()
+ if (mergeWrapper) {
+ store.dispatch(A.wallet.mergeWrapper(nextWallet))
+ } else {
+ sync()
+ }
break
default:
break
diff --git a/packages/blockchain-wallet-v4/src/remote/index.js b/packages/blockchain-wallet-v4/src/remote/index.js
index 845f7bcb156..5f45170cc5e 100755
--- a/packages/blockchain-wallet-v4/src/remote/index.js
+++ b/packages/blockchain-wallet-v4/src/remote/index.js
@@ -1,3 +1,5 @@
+// https://medium.com/@jaumepernas/your-data-is-loading-1425c6b76bf0
+
import { taggedSum } from 'daggy'
const Remote = taggedSum('Remote', {
diff --git a/packages/blockchain-wallet-v4/src/signer/bch.js b/packages/blockchain-wallet-v4/src/signer/bch.js
index bf558e8ceb2..29852202a14 100755
--- a/packages/blockchain-wallet-v4/src/signer/bch.js
+++ b/packages/blockchain-wallet-v4/src/signer/bch.js
@@ -50,10 +50,9 @@ export const sortSelection = selection => ({
outputs: Coin.bip69SortOutputs(selection.outputs)
})
-// signHDWallet :: network -> password -> wrapper -> selection -> Task selection
export const signHDWallet = curry(
- (network, secondPassword, wrapper, selection, coinDust) =>
- addHDWalletWIFS(network, secondPassword, wrapper, selection).map(
+ (securityModule, network, secondPassword, wrapper, selection, coinDust) =>
+ addHDWalletWIFS(securityModule, network, secondPassword, wrapper, selection).map(
signWithWIF(network, coinDust)
)
)
diff --git a/packages/blockchain-wallet-v4/src/signer/bsv.js b/packages/blockchain-wallet-v4/src/signer/bsv.js
index e4ea42f82d3..38f5cc38d24 100644
--- a/packages/blockchain-wallet-v4/src/signer/bsv.js
+++ b/packages/blockchain-wallet-v4/src/signer/bsv.js
@@ -43,10 +43,9 @@ export const sortSelection = selection => ({
outputs: Coin.bip69SortOutputs(selection.outputs)
})
-// signHDWallet :: network -> password -> wrapper -> selection -> Task selection
export const signHDWallet = curry(
- (network, secondPassword, wrapper, selection, coinDust) =>
- addHDWalletWIFS(network, secondPassword, wrapper, selection).map(
+ (securityModule, network, secondPassword, wrapper, selection, coinDust) =>
+ addHDWalletWIFS(securityModule, network, secondPassword, wrapper, selection).map(
signWithWIF(network, coinDust)
)
)
diff --git a/packages/blockchain-wallet-v4/src/signer/btc.js b/packages/blockchain-wallet-v4/src/signer/btc.js
index 745f7f43c41..ce8567bf170 100755
--- a/packages/blockchain-wallet-v4/src/signer/btc.js
+++ b/packages/blockchain-wallet-v4/src/signer/btc.js
@@ -37,10 +37,9 @@ export const sortSelection = selection => ({
outputs: Coin.bip69SortOutputs(selection.outputs)
})
-// signHDWallet :: network -> password -> wrapper -> selection -> Task selection
export const signHDWallet = curry(
- (network, secondPassword, wrapper, selection) =>
- addHDWalletWIFS(network, secondPassword, wrapper, selection).map(
+ (securityModule, network, secondPassword, wrapper, selection) =>
+ addHDWalletWIFS(securityModule, network, secondPassword, wrapper, selection).map(
signWithWIF(network)
)
)
diff --git a/packages/blockchain-wallet-v4/src/signer/eth.js b/packages/blockchain-wallet-v4/src/signer/eth.js
index ac312205866..69638042caf 100755
--- a/packages/blockchain-wallet-v4/src/signer/eth.js
+++ b/packages/blockchain-wallet-v4/src/signer/eth.js
@@ -1,6 +1,8 @@
import BigNumber from 'bignumber.js'
import EthereumTx from 'ethereumjs-tx'
import EthereumAbi from 'ethereumjs-abi'
+
+import { returnTask } from '../utils/functional'
import * as eth from '../utils/eth'
import Task from 'data.task'
import { curry } from 'ramda'
@@ -13,9 +15,9 @@ const toHex = value => {
}
export const signErc20 = curry(
- (network = 1, mnemonic, data, contractAddress) => {
+ (network = 1, securityModule, data, contractAddress) => {
const { index, to, amount, nonce, gasPrice, gasLimit } = data
- const privateKey = eth.getPrivateKey(mnemonic, index)
+ const privateKey = eth.getPrivateKey(securityModule, index)
const transferMethodHex = '0xa9059cbb'
const txParams = {
to: contractAddress,
@@ -36,9 +38,9 @@ export const signErc20 = curry(
}
)
-export const sign = curry((network = 1, mnemonic, data) => {
+export const sign = curry((network = 1, securityModule, data) => {
const { index, to, amount, nonce, gasPrice, gasLimit } = data
- const privateKey = eth.getPrivateKey(mnemonic, index)
+ const privateKey = eth.getPrivateKey(securityModule, index)
const txParams = {
to,
nonce: toHex(nonce),
@@ -95,20 +97,25 @@ export const serialize = (network, raw, signature) => {
return '0x' + tx.serialize().toString('hex')
}
-export const signLegacy = curry((network = 1, seedHex, data) => {
- const { index, to, amount, nonce, gasPrice, gasLimit } = data
- const privateKey = eth.getLegacyPrivateKey(seedHex, index)
- const txParams = {
- to,
- nonce: toHex(nonce),
- gasPrice: toHex(gasPrice),
- gasLimit: toHex(gasLimit),
- value: toHex(amount),
- chainId: network || 1
- }
+export const signLegacy = curry(
+ returnTask(async (network = 1, securityModule, secondPassword, data) => {
+ const { to, amount, nonce, gasPrice, gasLimit } = data
- const tx = new EthereumTx(txParams)
- tx.sign(privateKey)
- const rawTx = '0x' + tx.serialize().toString('hex')
- return Task.of(rawTx)
-})
+ const privateKey = await securityModule.deriveLegacyEthereumKey({
+ secondPassword
+ })
+
+ const txParams = {
+ to,
+ nonce: toHex(nonce),
+ gasPrice: toHex(gasPrice),
+ gasLimit: toHex(gasLimit),
+ value: toHex(amount),
+ chainId: network || 1
+ }
+
+ const tx = new EthereumTx(txParams)
+ tx.sign(privateKey)
+ return '0x' + tx.serialize().toString('hex')
+ })
+)
diff --git a/packages/blockchain-wallet-v4/src/signer/wifs.js b/packages/blockchain-wallet-v4/src/signer/wifs.js
index e345c5ab4da..fb3cc4af141 100755
--- a/packages/blockchain-wallet-v4/src/signer/wifs.js
+++ b/packages/blockchain-wallet-v4/src/signer/wifs.js
@@ -4,12 +4,17 @@ import Task from 'data.task'
import { Wrapper, Wallet } from '../types'
import * as Coin from '../coinSelection/coin'
-// addHDWalletWIFS :: network -> password -> wrapper -> selection -> Task selection
export const addHDWalletWIFS = curry(
- (network, secondPassword, wrapper, selection) => {
+ (securityModule, network, secondPassword, wrapper, selection) => {
const wallet = Wrapper.selectWallet(wrapper)
const deriveKey = coin =>
- Wallet.getHDPrivateKeyWIF(coin.path, secondPassword, network, wallet)
+ Wallet.getHDPrivateKeyWIF(
+ securityModule,
+ coin.path,
+ secondPassword,
+ network,
+ wallet
+ )
// .map(wif => Bitcoin.ECPair.fromWIF(wif, network))
.map(wif => set(Coin.priv, wif, coin))
const selectionWithKeys = traverseOf(
diff --git a/packages/blockchain-wallet-v4/src/signer/xlm.js b/packages/blockchain-wallet-v4/src/signer/xlm.js
index 4afd7c9d13e..c2d7c1206f5 100755
--- a/packages/blockchain-wallet-v4/src/signer/xlm.js
+++ b/packages/blockchain-wallet-v4/src/signer/xlm.js
@@ -2,8 +2,8 @@ import { getKeyPair } from '../utils/xlm'
import * as StellarSdk from 'stellar-sdk'
import Str from '@ledgerhq/hw-app-str'
-export const sign = ({ transaction }, mnemonic) => {
- const keyPair = getKeyPair(mnemonic)
+export const sign = async ({ secondPassword, securityModule }, transaction) => {
+ const keyPair = await getKeyPair({ secondPassword, securityModule })
transaction.sign(keyPair)
return transaction
}
diff --git a/packages/blockchain-wallet-v4/src/types/HDWallet.js b/packages/blockchain-wallet-v4/src/types/HDWallet.js
index d1581d73a57..91c5f350e99 100755
--- a/packages/blockchain-wallet-v4/src/types/HDWallet.js
+++ b/packages/blockchain-wallet-v4/src/types/HDWallet.js
@@ -3,7 +3,6 @@ import { pipe, compose, curry, is, range, map } from 'ramda'
import { view, over, traverseOf, traversed } from 'ramda-lens'
import Bitcoin from 'bitcoinjs-lib'
import BIP39 from 'bip39'
-import * as crypto from '../walletCrypto'
import Task from 'data.task'
import Type from './Type'
@@ -75,19 +74,10 @@ export const reviver = jsObject => {
return new HDWallet(jsObject)
}
-export const deriveAccountNodeAtIndex = (seedHex, index, network) => {
- let seed = BIP39.mnemonicToSeed(BIP39.entropyToMnemonic(seedHex))
- let masterNode = Bitcoin.HDNode.fromSeedBuffer(seed, network)
- return masterNode
- .deriveHardened(44)
- .deriveHardened(0)
- .deriveHardened(index)
-}
-
-export const generateAccount = curry((index, label, network, seedHex) => {
- let node = deriveAccountNodeAtIndex(seedHex, index, network)
+export const generateAccount = (key, label) => {
+ const node = Bitcoin.HDNode.fromBase58(key)
return HDAccount.fromJS(HDAccount.js(label, node, null))
-})
+}
// encrypt :: Number -> String -> String -> HDWallet -> Task Error HDWallet
export const encrypt = curry((iterations, sharedKey, password, hdWallet) => {
diff --git a/packages/blockchain-wallet-v4/src/types/HDWallet.spec.js b/packages/blockchain-wallet-v4/src/types/HDWallet.spec.js
index 9a7c12541ec..5fb7a93265f 100755
--- a/packages/blockchain-wallet-v4/src/types/HDWallet.spec.js
+++ b/packages/blockchain-wallet-v4/src/types/HDWallet.spec.js
@@ -18,27 +18,28 @@ describe('HDWallet', () => {
})
})
- // describe('createNew', () => {
- // const { wallet, mnemonic } = walletNewFixture
- // const hdWallet = HDWallet.createNew(mnemonic)
-
- // it('should generate the correct seed hex', () => {
- // expect(hdWallet.seedHex).toEqual(wallet.hd_wallets[0].seed_hex)
- // })
-
- // it('should have the correct first account', () => {
- // let firstAccount = HDWallet.toJS(hdWallet).accounts[0]
- // let accountFixtureNoLabels = R.set(R.lensProp('address_labels'), [], hdWalletFixture.accounts[0])
- // expect(firstAccount).toEqual(accountFixtureNoLabels)
- // })
-
- // it('should optionally set the first account label', () => {
- // let label = 'another label'
- // let hdWalletLabelled = HDWallet.createNew(mnemonic, { label })
- // let firstAccount = HDWallet.toJS(hdWalletLabelled).accounts[0]
- // expect(firstAccount.label).toEqual(label)
- // })
- // })
+ it(`generateAccount`, () => {
+ expect(
+ HDWallet.generateAccount(
+ `xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi`,
+ `label`
+ ).toJS()
+ ).toEqual({
+ label: 'label',
+ archived: false,
+ xpriv:
+ 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi',
+ xpub:
+ 'xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8',
+ address_labels: {},
+ cache: {
+ receiveAccount:
+ 'xpub68Gmy5EVb2BdFbj2LpWrk1M7obNuaPTpT5oh9QCCo5sRfqSHVYWex97WpDZzszdzHzxXDAzPLVSwybe4uPYkSk4G3gnrPqqkV9RyNzAcNJ1',
+ changeAccount:
+ 'xpub68Gmy5EVb2BdHTYHpekwGdcbBWax19w9HwA2DaADYvuCSSgt4YAErxxSN1KWSnmyqkwRNbnTj3XiUBKmHeC8rTjLRPjSULcDKQQgfgJDppq'
+ }
+ })
+ })
describe('serializer', () => {
it('compose(reviver, replacer) should be identity', () => {
diff --git a/packages/blockchain-wallet-v4/src/types/KVStoreEntry.js b/packages/blockchain-wallet-v4/src/types/KVStoreEntry.js
index a9ecbfb6dae..adb6119e20b 100755
--- a/packages/blockchain-wallet-v4/src/types/KVStoreEntry.js
+++ b/packages/blockchain-wallet-v4/src/types/KVStoreEntry.js
@@ -69,12 +69,27 @@ export const fromCredentials = curry((guid, sharedKey, password, network) => {
return fromKeys(key, enc)
})
+export const fromEntropy = ({ entropy, network }) => {
+ const d = BigInteger.fromBuffer(entropy)
+ const key = new Bitcoin.ECPair(d, null, { network })
+ const enc = key.d.toBuffer(32)
+ return fromKeys(key, enc)
+}
+
export const getMasterHDNode = curry((network, seedHex) => {
const mnemonic = BIP39.entropyToMnemonic(seedHex)
const masterhex = BIP39.mnemonicToSeed(mnemonic)
return Bitcoin.HDNode.fromSeedBuffer(masterhex, network)
})
+// BIP 43 purpose needs to be 31 bit or less. For lack of a BIP number
+// we take the first 31 bits of the SHA256 hash of a reverse domain.
+export const metadataPurpose =
+ crypto
+ .sha256('info.blockchain.metadata')
+ .slice(0, 4)
+ .readUInt32BE(0) & 0x7fffffff // 510742
+
export const deriveMetadataNode = masterHDNode => {
// BIP 43 purpose needs to be 31 bit or less. For lack of a BIP number
// we take the first 31 bits of the SHA256 hash of a reverse domain.
@@ -100,11 +115,6 @@ export const fromMasterHDNode = curry((masterHDNode, typeId) => {
return fromMetadataHDNode(metadataHDNode, typeId)
})
-export const fromHdWallet = curry((hdWallet, typeId) => {
- const masterHdNode = getMasterHDNode(hdWallet.seedHex)
- return fromMasterHDNode(masterHdNode, typeId)
-})
-
export const encrypt = curry((key, data) =>
crypto.encryptDataWithKey(data, key, null)
)
diff --git a/packages/blockchain-wallet-v4/src/types/Serializer.js b/packages/blockchain-wallet-v4/src/types/Serializer.js
index ac58af897d9..b73baf1e62e 100755
--- a/packages/blockchain-wallet-v4/src/types/Serializer.js
+++ b/packages/blockchain-wallet-v4/src/types/Serializer.js
@@ -19,6 +19,17 @@ import Remote from '../remote'
const serializer = {
replacer: function (key, value) {
+ // Without this, jsan swallows the stack trace.
+ if (value instanceof Error) {
+ return {
+ __serializedType__: `Error`,
+ data: {
+ message: value.message,
+ stack: value.stack
+ }
+ }
+ }
+
// Remove all functions from the state
if (value && typeof value === 'function') {
return ''
@@ -34,13 +45,17 @@ const serializer = {
return value
},
reviver: function (key, value) {
- if (
+ if (value && value.type === 'Buffer') {
+ return Buffer.from(value.data)
+ } else if (
typeof value === 'object' &&
value !== null &&
'__serializedType__' in value
) {
var data = value.data
switch (value.__serializedType__) {
+ case `Error`:
+ return Object.assign(new Error(data.message), { stack: data.stack })
case 'Wrapper':
return Wrapper.reviver(data)
case 'Wallet':
diff --git a/packages/blockchain-wallet-v4/src/types/Wallet.js b/packages/blockchain-wallet-v4/src/types/Wallet.js
index 489144cf36c..c7f37de200e 100755
--- a/packages/blockchain-wallet-v4/src/types/Wallet.js
+++ b/packages/blockchain-wallet-v4/src/types/Wallet.js
@@ -6,19 +6,9 @@ import Maybe from 'data.maybe'
import Bitcoin from 'bitcoinjs-lib'
import memoize from 'fast-memoize'
import BIP39 from 'bip39'
-import {
- compose,
- curry,
- map,
- is,
- pipe,
- __,
- concat,
- split,
- isNil,
- flip
-} from 'ramda'
+import { compose, concat, curry, map, is, pipe, __, split, isNil } from 'ramda'
import { traversed, traverseOf, over, view, set } from 'ramda-lens'
+
import * as crypto from '../walletCrypto'
import { shift, shiftIProp } from './util'
import Type from './Type'
@@ -273,15 +263,6 @@ export const importLegacyAddress = curry(
}
)
-// upgradeToHd :: String -> String -> String? -> Task Error Wallet
-export const upgradeToHd = curry(
- (mnemonic, firstLabel, password, network, wallet) => {
- return newHDWallet(mnemonic, password, wallet).chain(
- newHDAccount(firstLabel, password, network)
- )
- }
-)
-
// newHDWallet :: String -> String? -> Wallet -> Task Error Wallet
export const newHDWallet = curry((mnemonic, password, wallet) => {
let hdWallet = HDWallet.createNew(mnemonic)
@@ -293,26 +274,19 @@ export const newHDWallet = curry((mnemonic, password, wallet) => {
)
})
-// newHDAccount :: String -> String? -> Wallet -> Task Error Wallet
-export const newHDAccount = curry((label, password, network, wallet) => {
- let hdWallet = HDWalletList.selectHDWallet(selectHdWallets(wallet))
- let index = hdWallet.accounts.size
+export const newHDAccount = curry((index, key, label, password, wallet) => {
let appendAccount = curry((w, account) => {
- let accountsLens = compose(
+ const accountsLens = compose(
hdWallets,
HDWalletList.hdwallet,
HDWallet.accounts
)
- let accountWithIndex = set(HDAccount.index, index, account)
+
+ const accountWithIndex = set(HDAccount.index, index, account)
return over(accountsLens, accounts => accounts.push(accountWithIndex), w)
})
- return applyCipher(
- wallet,
- password,
- flip(crypto.decryptSecPass),
- hdWallet.seedHex
- )
- .map(HDWallet.generateAccount(index, label, network))
+
+ return Task.of(HDWallet.generateAccount(key, label))
.chain(applyCipher(wallet, password, HDAccount.encrypt))
.map(appendAccount(wallet))
})
diff --git a/packages/blockchain-wallet-v4/src/types/Wallet.spec.js b/packages/blockchain-wallet-v4/src/types/Wallet.spec.js
index 467d8b6e19f..a428ea739fe 100755
--- a/packages/blockchain-wallet-v4/src/types/Wallet.spec.js
+++ b/packages/blockchain-wallet-v4/src/types/Wallet.spec.js
@@ -1,6 +1,7 @@
import * as R from 'ramda'
import { Address, Wallet, AddressMap, serializer } from './index'
import * as crypto from '../walletCrypto/index'
+import { taskToPromise } from '../utils/functional'
const walletFixture = require('./__mocks__/wallet.v3')
const walletFixtureSecpass = require('./__mocks__/wallet.v3-secpass')
@@ -107,6 +108,19 @@ describe('Wallet', () => {
})
})
+ it(`newHDAccount`, async () => {
+ const index = 1
+ const key = `xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi`
+ const label = `label`
+ const password = undefined
+
+ expect(
+ (await taskToPromise(
+ Wallet.newHDAccount(index, key, label, password, wallet)
+ )).toJS()
+ ).toMatchSnapshot()
+ })
+
describe('setAddressLabel', () => {
it('should set a new address label', () => {
let addr = '14mQxLtEagsS8gYsdWJbzthFFuPDqDgtxQ'
@@ -178,14 +192,4 @@ describe('Wallet', () => {
expect(string2).toEqual(string)
})
})
-
- // describe('createNew', () => {
- // const { mnemonic } = walletNewFixture
-
- // it('should create a new wallet', () => {
- // let { guid, sharedKey } = walletNewFixture.wallet
- // let wallet = Wallet.createNew(guid, sharedKey, mnemonic)
- // expect(Wallet.toJS(wallet)).toEqual(walletNewFixture.wallet)
- // })
- // })
})
diff --git a/packages/blockchain-wallet-v4/src/types/Wrapper.js b/packages/blockchain-wallet-v4/src/types/Wrapper.js
index 49c99196937..36e735bbd30 100755
--- a/packages/blockchain-wallet-v4/src/types/Wrapper.js
+++ b/packages/blockchain-wallet-v4/src/types/Wrapper.js
@@ -48,6 +48,17 @@ export const selectRealAuthType = view(realAuthType)
export const selectWallet = view(wallet)
export const selectSyncPubKeys = view(syncPubKeys)
+const keyPaths = [[`password`], [`wallet`, `hd_wallets`, 0, `seedHex`]]
+
+// Remove some properties before transferring between the Security and Main
+// Processes.
+export const redact = wrapper =>
+ wrapper.withMutations(mutable => {
+ keyPaths.forEach(keyPath => {
+ mutable.deleteIn(keyPath)
+ })
+ })
+
// traverseWallet :: Monad m => (a -> m a) -> (Wallet -> m Wallet) -> Wrapper
export const traverseWallet = curry((of, f, wrapper) =>
of(wrapper).chain(traverseOf(wallet, of, f))
diff --git a/packages/blockchain-wallet-v4/src/types/Wrapper.spec.js b/packages/blockchain-wallet-v4/src/types/Wrapper.spec.js
index 54a367aa496..accf27e8fb8 100755
--- a/packages/blockchain-wallet-v4/src/types/Wrapper.spec.js
+++ b/packages/blockchain-wallet-v4/src/types/Wrapper.spec.js
@@ -5,6 +5,10 @@ const wrapperFixture = require('./__mocks__/wrapper.v3')
describe('Wrapper', () => {
const myWrapper = Wrapper.fromJS(wrapperFixture)
+ it(`redact`, () => {
+ expect(Wrapper.redact(myWrapper)).toMatchSnapshot()
+ })
+
describe('serializer', () => {
it('compose(replacer, reviver) should be identity', () => {
const string = JSON.stringify(myWrapper)
diff --git a/packages/blockchain-wallet-v4/src/types/__snapshots__/Wallet.spec.js.snap b/packages/blockchain-wallet-v4/src/types/__snapshots__/Wallet.spec.js.snap
new file mode 100644
index 00000000000..74e7c8a5a1c
--- /dev/null
+++ b/packages/blockchain-wallet-v4/src/types/__snapshots__/Wallet.spec.js.snap
@@ -0,0 +1,87 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Wallet newHDAccount 1`] = `
+Object {
+ "address_book": Object {},
+ "addresses": Object {
+ "14mQxLtEagsS8gYsdWJbzthFFuPDqDgtxQ": Object {
+ "addr": "14mQxLtEagsS8gYsdWJbzthFFuPDqDgtxQ",
+ "created_device_name": "javascript_web",
+ "created_device_version": "3.0",
+ "created_time": 1492721432222,
+ "label": "labeled_imported",
+ "priv": "BpD2ZuJjZ8PJPpDX6ZmsKFsXHkL7XV3dt385zghMfF6C",
+ "tag": 0,
+ },
+ "19XmKRY66VnUn5irHAafyoTfiwFuGLUxKF": Object {
+ "addr": "19XmKRY66VnUn5irHAafyoTfiwFuGLUxKF",
+ "created_device_name": "javascript_web",
+ "created_device_version": "3.0",
+ "created_time": 1492721419269,
+ "label": "",
+ "priv": "38W3vsxt246Lhawvz81y2HQVnsJrBUX87jSTUUnixPsE",
+ "tag": 0,
+ },
+ "1JD73aGSdeqKUjJ4ntP4eCyUiuZ3ogJE1u": Object {
+ "addr": "1JD73aGSdeqKUjJ4ntP4eCyUiuZ3ogJE1u",
+ "created_device_name": "javascript_web",
+ "created_device_version": "3.0",
+ "created_time": 1492721461228,
+ "label": "",
+ "priv": null,
+ "tag": 0,
+ },
+ },
+ "double_encryption": false,
+ "guid": "50dae286-e42e-4d67-8419-d5dcc563746c",
+ "hd_wallets": Array [
+ Object {
+ "accounts": Array [
+ Object {
+ "address_labels": Object {
+ "0": Object {
+ "index": 0,
+ "label": "labeled_address",
+ },
+ },
+ "archived": false,
+ "cache": Object {
+ "changeAccount": "xpub6F41z8MqNcJMwmeUExdCv7UXvYBEgQB29SWq9jyxuZ7WefmSTWcwXB6NRAJkGCkB3L1Eu4ttzWnPVKZ6REissrQ4i6p8gTi9j5YwDLxmZ8p",
+ "receiveAccount": "xpub6F41z8MqNcJMvKQgAd5QE2QYo32cocYigWp1D8726ykMmaMqvtqLkvuL1NqGuUJvU3aWyJaV2J4V6sD7Pv59J3tYGZdYRSx8gU7EG8ZuPSY",
+ },
+ "index": 0,
+ "label": "My Bitcoin Wallet",
+ "xpriv": "xprv9yL1ousLjQQzGNBAYykaT8J3U626NV6zbLYkRv8rvUDpY4f1RnrvAXQneGXC9UNuNvGXX4j6oHBK5KiV2hKevRxY5ntis212oxjEL11ysuG",
+ "xpub": "xpub6CKNDRQEZmyHUrFdf1HapGEn27ramwpqxZUMEJYUUokoQrz9yLBAiKjGVWDuiCT39udj1r3whqQN89Tar5KrojH8oqSy7ytzJKW8gwmhwD3",
+ },
+ Object {
+ "address_labels": Object {},
+ "archived": false,
+ "cache": Object {
+ "changeAccount": "xpub68Gmy5EVb2BdHTYHpekwGdcbBWax19w9HwA2DaADYvuCSSgt4YAErxxSN1KWSnmyqkwRNbnTj3XiUBKmHeC8rTjLRPjSULcDKQQgfgJDppq",
+ "receiveAccount": "xpub68Gmy5EVb2BdFbj2LpWrk1M7obNuaPTpT5oh9QCCo5sRfqSHVYWex97WpDZzszdzHzxXDAzPLVSwybe4uPYkSk4G3gnrPqqkV9RyNzAcNJ1",
+ },
+ "index": 1,
+ "label": "label",
+ "xpriv": "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi",
+ "xpub": "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8",
+ },
+ ],
+ "default_account_idx": 0,
+ "mnemonic_verified": false,
+ "passphrase": "",
+ "seedHex": "6a4d9524d413fdf69ca1b5664d1d6db0",
+ },
+ ],
+ "metadataHDNode": "xprv9tygGQP8be7uzNm5Czuy41juTK9pUKnWyZtDxgbmSEcCYa9VdvvtSknEyiKitqqm2TMv14NjXPQ68XLwSdH6Scc5GwXoZ31yRZZysxhVGU7",
+ "options": Object {
+ "fee_per_kb": 10000,
+ "html5_notifications": false,
+ "logout_time": 600000,
+ "pbkdf2_iterations": 5000,
+ },
+ "sharedKey": "8a260b2b-5257-4357-ac56-7a7efca323ea",
+ "tx_names": Array [],
+ "tx_notes": Object {},
+}
+`;
diff --git a/packages/blockchain-wallet-v4/src/types/__snapshots__/Wrapper.spec.js.snap b/packages/blockchain-wallet-v4/src/types/__snapshots__/Wrapper.spec.js.snap
new file mode 100644
index 00000000000..583ed7ac6d2
--- /dev/null
+++ b/packages/blockchain-wallet-v4/src/types/__snapshots__/Wrapper.spec.js.snap
@@ -0,0 +1,83 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Wrapper redact 1`] = `
+Immutable.Map {
+ "sync_pubkeys": false,
+ "payload_checksum": "mypayloadchecksum",
+ "storage_token": "mytoken",
+ "version": 3,
+ "language": "en",
+ "wallet": Immutable.Map {
+ "addresses": Immutable.Map {
+ "19XmKRY66VnUn5irHAafyoTfiwFuGLUxKF": Immutable.Map {
+ "addr": "19XmKRY66VnUn5irHAafyoTfiwFuGLUxKF",
+ "priv": "38W3vsxt246Lhawvz81y2HQVnsJrBUX87jSTUUnixPsE",
+ "tag": 0,
+ "label": "",
+ "created_time": 1492721419269,
+ "created_device_name": "javascript_web",
+ "created_device_version": "3.0",
+ },
+ "14mQxLtEagsS8gYsdWJbzthFFuPDqDgtxQ": Immutable.Map {
+ "addr": "14mQxLtEagsS8gYsdWJbzthFFuPDqDgtxQ",
+ "priv": "BpD2ZuJjZ8PJPpDX6ZmsKFsXHkL7XV3dt385zghMfF6C",
+ "tag": 0,
+ "label": "labeled_imported",
+ "created_time": 1492721432222,
+ "created_device_name": "javascript_web",
+ "created_device_version": "3.0",
+ },
+ "1JD73aGSdeqKUjJ4ntP4eCyUiuZ3ogJE1u": Immutable.Map {
+ "addr": "1JD73aGSdeqKUjJ4ntP4eCyUiuZ3ogJE1u",
+ "priv": null,
+ "tag": 0,
+ "label": "",
+ "created_time": 1492721461228,
+ "created_device_name": "javascript_web",
+ "created_device_version": "3.0",
+ },
+ },
+ "tx_notes": Immutable.Map {},
+ "guid": "50dae286-e42e-4d67-8419-d5dcc563746c",
+ "metadataHDNode": "xprv9tygGQP8be7uzNm5Czuy41juTK9pUKnWyZtDxgbmSEcCYa9VdvvtSknEyiKitqqm2TMv14NjXPQ68XLwSdH6Scc5GwXoZ31yRZZysxhVGU7",
+ "tx_names": Immutable.List [],
+ "double_encryption": false,
+ "address_book": Immutable.Map {},
+ "hd_wallets": Immutable.List [
+ Immutable.Map {
+ "accounts": Immutable.List [
+ Immutable.Map {
+ "label": "My Bitcoin Wallet",
+ "archived": false,
+ "xpriv": "xprv9yL1ousLjQQzGNBAYykaT8J3U626NV6zbLYkRv8rvUDpY4f1RnrvAXQneGXC9UNuNvGXX4j6oHBK5KiV2hKevRxY5ntis212oxjEL11ysuG",
+ "xpub": "xpub6CKNDRQEZmyHUrFdf1HapGEn27ramwpqxZUMEJYUUokoQrz9yLBAiKjGVWDuiCT39udj1r3whqQN89Tar5KrojH8oqSy7ytzJKW8gwmhwD3",
+ "address_labels": Immutable.Map {
+ "0": Immutable.Map {
+ "index": 0,
+ "label": "labeled_address",
+ },
+ },
+ "cache": Immutable.Map {
+ "receiveAccount": "xpub6F41z8MqNcJMvKQgAd5QE2QYo32cocYigWp1D8726ykMmaMqvtqLkvuL1NqGuUJvU3aWyJaV2J4V6sD7Pv59J3tYGZdYRSx8gU7EG8ZuPSY",
+ "changeAccount": "xpub6F41z8MqNcJMwmeUExdCv7UXvYBEgQB29SWq9jyxuZ7WefmSTWcwXB6NRAJkGCkB3L1Eu4ttzWnPVKZ6REissrQ4i6p8gTi9j5YwDLxmZ8p",
+ },
+ "index": 0,
+ },
+ ],
+ "passphrase": "",
+ "mnemonic_verified": false,
+ "default_account_idx": 0,
+ },
+ ],
+ "sharedKey": "8a260b2b-5257-4357-ac56-7a7efca323ea",
+ "options": Immutable.Map {
+ "pbkdf2_iterations": 5000,
+ "fee_per_kb": 10000,
+ "html5_notifications": false,
+ "logout_time": 600000,
+ },
+ },
+ "war_checksum": "mychecksum",
+ "pbkdf2_iterations": "",
+}
+`;
diff --git a/packages/blockchain-wallet-v4/src/utils/eth.js b/packages/blockchain-wallet-v4/src/utils/eth.js
index 72a5567e509..a8f4bedd8c1 100755
--- a/packages/blockchain-wallet-v4/src/utils/eth.js
+++ b/packages/blockchain-wallet-v4/src/utils/eth.js
@@ -1,7 +1,5 @@
import * as Exchange from '../exchange'
import { prop, path } from 'ramda'
-import BIP39 from 'bip39'
-import Bitcoin from 'bitcoinjs-lib'
import EthHd from 'ethereumjs-wallet/hdkey'
import EthUtil from 'ethereumjs-util'
import BigNumber from 'bignumber.js'
@@ -11,43 +9,26 @@ import BigNumber from 'bignumber.js'
*/
export const isValidAddress = address => /^0x[a-fA-F0-9]{40}$/.test(address)
-/**
- * @param {string} mnemonic
- * @param {integer} index
- */
-export const getPrivateKey = (mnemonic, index) => {
- const seed = BIP39.mnemonicToSeed(mnemonic)
- const account = Bitcoin.HDNode.fromSeedBuffer(seed)
- .deriveHardened(44)
- .deriveHardened(60)
- .deriveHardened(0)
- .derive(0)
- .derive(index)
- .toBase58()
- return EthHd.fromExtendedKey(account)
- .getWallet()
- .getPrivateKey()
-}
+export const getPrivateKey = async (
+ { deriveBIP32Key },
+ secondPassword,
+ index
+) => {
+ const key = await deriveBIP32Key(
+ { secondPassword },
+ `m/44'/60'/0'/0/${index}`
+ )
-// Derivation error using seedHex directly instead of seed derived from mnemonic derived from seedHex
-export const getLegacyPrivateKey = seedHex => {
- return deriveChildLegacy(0, seedHex)
+ return EthHd.fromExtendedKey(key)
.getWallet()
.getPrivateKey()
}
-const deriveChildLegacy = (index, seed) => {
- const derivationPath = "m/44'/60'/0'/0"
- return EthHd.fromMasterSeed(seed)
- .derivePath(derivationPath)
- .deriveChild(index)
-}
-
export const privateKeyToAddress = pk =>
EthUtil.toChecksumAddress(EthUtil.privateToAddress(pk).toString('hex'))
-export const deriveAddress = (mnemonic, index) =>
- privateKeyToAddress(getPrivateKey(mnemonic, index))
+export const deriveAddress = async (...args) =>
+ privateKeyToAddress(await getPrivateKey(...args))
export const deriveAddressFromXpub = xpub => {
const ethPublic = EthHd.fromExtendedKey(xpub)
diff --git a/packages/blockchain-wallet-v4/src/utils/functional.js b/packages/blockchain-wallet-v4/src/utils/functional.js
index e2516ce8370..6d4b9595f06 100755
--- a/packages/blockchain-wallet-v4/src/utils/functional.js
+++ b/packages/blockchain-wallet-v4/src/utils/functional.js
@@ -1,12 +1,23 @@
+import Task from 'data.task'
import { compose } from 'ramda'
import { call } from 'redux-saga/effects'
// Used as default value for functions
export const noop = () => {}
-const taskToPromise = t =>
+export const promiseToTask = promise =>
+ new Task((reject, resolve) => promise.then(resolve, reject))
+
+export const taskToPromise = t =>
new Promise((resolve, reject) => t.fork(reject, resolve))
+// Transform a function that returns a promise into one that returns a Task.
+export const returnTask = func => {
+ const newFunction = (...args) => promiseToTask(func(...args))
+ Object.defineProperty(newFunction, `length`, { value: func.length })
+ return newFunction
+}
+
export const callTask = function * (task) {
return yield call(
compose(
diff --git a/packages/blockchain-wallet-v4/src/utils/xlm.js b/packages/blockchain-wallet-v4/src/utils/xlm.js
index 820dadc079e..6b1afddb9d1 100755
--- a/packages/blockchain-wallet-v4/src/utils/xlm.js
+++ b/packages/blockchain-wallet-v4/src/utils/xlm.js
@@ -2,8 +2,6 @@ import { BigNumber } from 'bignumber.js'
import * as StellarSdk from 'stellar-sdk'
import queryString from 'query-string'
import { assoc } from 'ramda'
-import BIP39 from 'bip39'
-import * as ed25519 from 'ed25519-hd-key'
export const calculateEffectiveBalance = (balance, reserve, fee) =>
new BigNumber(balance)
@@ -48,9 +46,14 @@ export const decodeXlmURI = uri => {
return { address: destination, amount, memo, note: msg }
}
-export const getKeyPair = mnemonic => {
- const seed = BIP39.mnemonicToSeed(mnemonic)
- const seedHex = seed.toString('hex')
- const masterKey = ed25519.derivePath("m/44'/148'/0'", seedHex)
+export const getKeyPair = async ({
+ secondPassword,
+ securityModule: { deriveSLIP10ed25519Key }
+}) => {
+ const masterKey = await deriveSLIP10ed25519Key(
+ { secondPassword },
+ `m/44'/148'/0'`
+ )
+
return StellarSdk.Keypair.fromRawEd25519Seed(masterKey.key)
}
diff --git a/packages/main-process/babel.config.js b/packages/main-process/babel.config.js
index 7b2631c7211..e339b510b49 100644
--- a/packages/main-process/babel.config.js
+++ b/packages/main-process/babel.config.js
@@ -1,11 +1,21 @@
+const path = require(`path`)
+
+const resolve = directory => path.resolve(__dirname, directory)
+
module.exports = {
presets: ['@babel/preset-env', '@babel/preset-react'],
plugins: [
'@babel/plugin-proposal-class-properties',
'@babel/plugin-proposal-object-rest-spread',
'babel-plugin-styled-components',
- ['module-resolver', { root: ['./src'], alias: { data: './src/data' } }],
- ['react-intl', { messagesDir: './build/extractedMessages' }]
+ [
+ 'module-resolver',
+ {
+ root: [resolve('src')],
+ alias: { data: resolve('src/data') }
+ }
+ ],
+ ['react-intl', { messagesDir: resolve('build/extractedMessages') }]
],
ignore: [],
env: {
@@ -18,8 +28,14 @@ module.exports = {
'@babel/plugin-proposal-class-properties',
'@babel/plugin-proposal-object-rest-spread',
'babel-plugin-styled-components',
- ['module-resolver', { root: ['./src'], alias: { data: './src/data' } }],
- ['react-intl', { messagesDir: './build/extractedMessages' }]
+ [
+ 'module-resolver',
+ {
+ root: [resolve('src')],
+ alias: { data: resolve('src/data') }
+ }
+ ],
+ ['react-intl', { messagesDir: resolve('build/extractedMessages') }]
]
},
development: {
@@ -31,7 +47,13 @@ module.exports = {
'@babel/plugin-proposal-class-properties',
'@babel/plugin-proposal-object-rest-spread',
'babel-plugin-styled-components',
- ['module-resolver', { root: ['./src'], alias: { data: './src/data' } }],
+ [
+ 'module-resolver',
+ {
+ root: [resolve('src')],
+ alias: { data: resolve('src/data') }
+ }
+ ],
'react-hot-loader/babel'
]
}
diff --git a/packages/main-process/package.json b/packages/main-process/package.json
index d304a44ea17..ceb8e92c95e 100644
--- a/packages/main-process/package.json
+++ b/packages/main-process/package.json
@@ -1,5 +1,5 @@
{
- "name": "blockchain-wallet-v4-frontend",
+ "name": "main-process",
"version": "0.1.0",
"description": "Frontend wallet application.",
"license": "AGPL-3.0-or-later",
@@ -9,23 +9,11 @@
},
"main": "index.js",
"scripts": {
- "analyze": "cross-env-shell ANALYZE=true NODE_ENV=production webpack-cli --config webpack.config.ci.js",
- "build:dev": "cross-env-shell NODE_ENV=development webpack-cli --config webpack.config.dev.js --progress --colors",
- "build:prod": "cross-env-shell NODE_ENV=production webpack-cli --config webpack.config.dev.js --progress --colors",
- "build:staging": "cross-env-shell NODE_ENV=staging webpack-cli --config webpack.config.dev.js --progress --colors",
- "build:testnet": "cross-env-shell NODE_ENV=testnet webpack-cli --config webpack.config.dev.js --progress --colors",
"ci:coverage:frontend": "yarn coverage --runInBand",
"ci:test:frontend": "yarn test --runInBand",
- "ci:compile": "cross-env-shell NODE_ENV=production webpack-cli --config webpack.config.ci.js --display-error-details",
"clean": "cross-env rimraf node_modules && rimraf build",
"coverage": "cross-env ./../../node_modules/.bin/jest --coverage",
- "debug:prod": "cross-env-shell NODE_ENV=production webpack-dev-server --config webpack.debug.js --progress --colors",
"link:resolved:paths": "ln -sf $(pwd)/src/** ./node_modules && ln -sf $(pwd)/../../packages/** ./node_modules",
- "manage:translations": "yarn build:prod && node ./translationRunner.js",
- "start:dev": "cross-env-shell NODE_ENV=development webpack-dev-server --config webpack.config.dev.js --progress --colors --watch --devtool cheap-module-source-map",
- "start:prod": "cross-env-shell DISABLE_SSL=true NODE_ENV=production webpack-dev-server --config webpack.config.dev.js --progress --colors --watch --devtool cheap-module-source-map",
- "start:staging": "cross-env-shell NODE_ENV=staging webpack-dev-server --config webpack.config.dev.js --progress --colors --watch --devtool cheap-module-source-map",
- "start:testnet": "cross-env-shell NODE_ENV=testnet webpack-dev-server --config webpack.config.dev.js --progress --colors --watch --devtool cheap-module-source-map",
"test": "cross-env ./../../node_modules/.bin/jest --silent",
"test:build": "echo 'No precomplilation required for tests to execute.'",
"test:debug": "cross-env node --inspect-brk ./../../node_modules/.bin/jest --runInBand",
@@ -143,6 +131,7 @@
"rxjs": "6.5.2",
"sanitize-html": "1.20.1",
"styled-components": "4.2.0",
+ "web-microkernel": "1.0.0",
"zxcvbn": "4.4.2"
}
}
diff --git a/packages/main-process/src/IPC/Middleware.js b/packages/main-process/src/IPC/Middleware.js
new file mode 100644
index 00000000000..26a7189715c
--- /dev/null
+++ b/packages/main-process/src/IPC/Middleware.js
@@ -0,0 +1,54 @@
+import * as router from 'connected-react-router'
+
+import * as coreTypes from 'blockchain-wallet-v4/src/redux/actionTypes'
+import * as types from '../data/actionTypes'
+
+const alreadyForwarded = ({ meta }) => meta && meta.forwarded
+
+const dispatchToSecurityProcess = ({ securityProcess }, action) => {
+ securityProcess.dispatch(action)
+}
+
+const dispatchToRootProcess = ({ rootProcessDispatch }, action) => {
+ rootProcessDispatch(action)
+}
+
+const ROOT_LOCATION_CHANGE = ({ rootProcessDispatch }, { payload }) => {
+ rootProcessDispatch({ type: router.LOCATION_CHANGE, payload })
+}
+
+const handlers = {
+ // Security Center needs settings.
+ [coreTypes.settings.FETCH_SETTINGS_FAILURE]: dispatchToSecurityProcess,
+ [coreTypes.settings.FETCH_SETTINGS_LOADING]: dispatchToSecurityProcess,
+ [coreTypes.settings.FETCH_SETTINGS_SUCCESS]: dispatchToSecurityProcess,
+
+ // Tell the Security Process to merge our wrapper with its own.
+ [coreTypes.wallet.MERGE_WRAPPER]: dispatchToSecurityProcess,
+
+ // Report a location change to the Root Process instead of processing it
+ // ourselves.
+ ROOT_LOCATION_CHANGE,
+
+ // Inform the Root Process about routing changes so that it can switch the
+ // appropriate process to the foreground.
+ [router.LOCATION_CHANGE]: dispatchToRootProcess,
+
+ // Tell the Security Process to reload itself when we do.
+ [types.auth.LOGOUT]: dispatchToSecurityProcess
+}
+
+export default ({ imports }) => () => next => action => {
+ const { type } = action
+
+ if (!alreadyForwarded(action)) {
+ if (type in handlers) {
+ handlers[type](imports, action)
+ } else if (type.startsWith(`@DATA.PREFERENCES.`)) {
+ // The Security Process handles persistence for preferences.
+ dispatchToSecurityProcess(imports, action)
+ }
+ }
+
+ return next(action)
+}
diff --git a/packages/main-process/src/IPC/index.js b/packages/main-process/src/IPC/index.js
new file mode 100644
index 00000000000..a043739b6c4
--- /dev/null
+++ b/packages/main-process/src/IPC/index.js
@@ -0,0 +1,28 @@
+import { serializer } from 'blockchain-wallet-v4/src/types'
+import Middleware from './Middleware'
+import * as kernel from 'web-microkernel'
+
+export default configureStore => () =>
+ new Promise(async resolve => {
+ const exportedFunction = async imports => {
+ const middleware = Middleware({ imports })
+ const root = await configureStore({ imports, middleware })
+
+ const dispatch = action => {
+ root.store.dispatch({
+ ...action,
+ meta: { ...action.meta, forwarded: true }
+ })
+ }
+
+ resolve(root)
+ return { dispatch }
+ }
+
+ const connection = await kernel.ChildProcess(
+ { reviver: serializer.reviver },
+ exportedFunction
+ )
+
+ connection.addEventListener(`error`, console.error)
+ })
diff --git a/packages/main-process/src/components/TransactionListItem/__snapshots__/index.spec.js.snap b/packages/main-process/src/components/TransactionListItem/__snapshots__/index.spec.js.snap
index bdf8f699027..a00c843b01d 100644
--- a/packages/main-process/src/components/TransactionListItem/__snapshots__/index.spec.js.snap
+++ b/packages/main-process/src/components/TransactionListItem/__snapshots__/index.spec.js.snap
@@ -170,6 +170,7 @@ exports[`ListItemContainer renders correctly 1`] = `
"deleteHdAddressLabel": [Function],
"deleteLegacyAddress": [Function],
"deleteWrapper": [Function],
+ "mergeWrapper": [Function],
"refreshWrapper": [Function],
"setAccountArchived": [Function],
"setAccountLabel": [Function],
diff --git a/packages/main-process/src/data/analytics/sagas.js b/packages/main-process/src/data/analytics/sagas.js
index feafb079173..a875b91dcaf 100644
--- a/packages/main-process/src/data/analytics/sagas.js
+++ b/packages/main-process/src/data/analytics/sagas.js
@@ -44,18 +44,7 @@ export default ({ api }) => {
}
const generateUniqueUserID = function * () {
- const defaultHDWallet = yield select(
- selectors.core.wallet.getDefaultHDWallet
- )
- const userId = yield call(waitForUserId)
- if (userId) return userId
- const { seedHex } = defaultHDWallet
- const mnemonic = BIP39.entropyToMnemonic(seedHex)
- const masterhex = BIP39.mnemonicToSeed(mnemonic)
- const masterHDNode = Bitcoin.HDNode.fromSeedBuffer(masterhex)
- let hash = crypto.sha256('info.blockchain.matomo')
- let purpose = hash.slice(0, 4).readUInt32BE(0) & 0x7fffffff
- return masterHDNode.deriveHardened(purpose).getAddress()
+ return yield call(waitForUserId) || ``
}
const initUserSession = function * () {
diff --git a/packages/main-process/src/data/auth/actionTypes.js b/packages/main-process/src/data/auth/actionTypes.js
index 0de985229bb..14e453788a5 100644
--- a/packages/main-process/src/data/auth/actionTypes.js
+++ b/packages/main-process/src/data/auth/actionTypes.js
@@ -3,6 +3,7 @@ export const DEAUTHORIZE_BROWSER = 'DEAUTHORIZE_BROWSER'
export const LOGIN = 'LOGIN'
export const LOGIN_FAILURE = 'LOGIN_FAILURE'
export const LOGIN_LOADING = 'LOGIN_LOADING'
+export const LOGIN_ROUTINE = 'LOGIN_ROUTINE'
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS'
export const LOGOUT = 'LOGOUT'
export const LOGOUT_CLEAR_REDUX_STORE = 'LOGOUT_CLEAR_REDUX_STORE'
diff --git a/packages/main-process/src/data/auth/actions.js b/packages/main-process/src/data/auth/actions.js
index 63600f6a11a..f1845c62e56 100644
--- a/packages/main-process/src/data/auth/actions.js
+++ b/packages/main-process/src/data/auth/actions.js
@@ -7,6 +7,12 @@ export const login = (guid, password, code, sharedKey, mobileLogin) => ({
payload: { guid, password, code, sharedKey, mobileLogin }
})
export const loginLoading = () => ({ type: AT.LOGIN_LOADING })
+
+export const loginRoutine = (mobileLogin = false, firstLogin = false) => ({
+ type: AT.LOGIN_ROUTINE,
+ payload: { firstLogin, mobileLogin }
+})
+
export const loginSuccess = () => ({ type: AT.LOGIN_SUCCESS, payload: {} })
export const loginFailure = err => ({
type: AT.LOGIN_FAILURE,
diff --git a/packages/main-process/src/data/auth/sagaRegister.js b/packages/main-process/src/data/auth/sagaRegister.js
index 7bff80754c1..6d1e80ba8dc 100644
--- a/packages/main-process/src/data/auth/sagaRegister.js
+++ b/packages/main-process/src/data/auth/sagaRegister.js
@@ -2,12 +2,13 @@ import { takeLatest } from 'redux-saga/effects'
import * as AT from './actionTypes'
import sagas from './sagas'
-export default ({ api, coreSagas }) => {
- const authSagas = sagas({ api, coreSagas })
+export default (...args) => {
+ const authSagas = sagas(...args)
return function * authSaga () {
yield takeLatest(AT.DEAUTHORIZE_BROWSER, authSagas.deauthorizeBrowser)
yield takeLatest(AT.LOGIN, authSagas.login)
+ yield takeLatest(AT.LOGIN_ROUTINE, authSagas.loginRoutineSaga)
yield takeLatest(AT.LOGOUT, authSagas.logout)
yield takeLatest(
AT.LOGOUT_CLEAR_REDUX_STORE,
diff --git a/packages/main-process/src/data/auth/sagas.js b/packages/main-process/src/data/auth/sagas.js
index cde0d1b4948..717fb471036 100644
--- a/packages/main-process/src/data/auth/sagas.js
+++ b/packages/main-process/src/data/auth/sagas.js
@@ -109,14 +109,8 @@ export default ({ api, coreSagas }) => {
)).getOrElse(false)
if (userFlowSupported) yield put(actions.modules.profile.signIn())
}
- const loginRoutineSaga = function * (mobileLogin, firstLogin) {
+ const loginRoutineSaga = function * ({ payload: { mobileLogin, firstLogin } }) {
try {
- // If needed, the user should upgrade its wallet before being able to open the wallet
- const isHdWallet = yield select(selectors.core.wallet.isHdWallet)
- if (!isHdWallet) {
- yield call(upgradeWalletSaga)
- }
- yield put(actions.auth.authenticate())
yield call(coreSagas.kvStore.root.fetchRoot, askSecondPasswordEnhancer)
// If there was no eth metadata kv store entry, we need to create one and that requires the second password.
yield call(
@@ -141,9 +135,6 @@ export default ({ api, coreSagas }) => {
const guid = yield select(selectors.core.wallet.getGuid)
// store guid in cache for future login
yield put(actions.cache.guidEntered(guid))
- // reset auth type and clear previous login form state
- yield put(actions.auth.setAuthType(0))
- yield put(actions.form.destroy('login'))
// set payload language to settings language
const language = yield select(selectors.preferences.getLanguage)
yield put(actions.modules.settings.updateLanguage(language))
@@ -224,7 +215,7 @@ export default ({ api, coreSagas }) => {
password,
code
})
- yield call(loginRoutineSaga, mobileLogin)
+ yield put(actions.auth.loginRoutine(mobileLogin))
} catch (error) {
const initialError = prop('initial_error', error)
const authRequired = prop('authorization_required', error)
@@ -247,7 +238,7 @@ export default ({ api, coreSagas }) => {
session,
password
})
- yield call(loginRoutineSaga, mobileLogin)
+ yield put(actions.auth.loginRoutine(mobileLogin))
} catch (error) {
if (error && error.auth_type > 0) {
yield put(actions.auth.setAuthType(error.auth_type))
@@ -333,7 +324,7 @@ export default ({ api, coreSagas }) => {
yield put(actions.auth.registerLoading())
yield call(coreSagas.wallet.createWalletSaga, action.payload)
yield put(actions.alerts.displaySuccess(C.REGISTER_SUCCESS))
- yield call(loginRoutineSaga, false, true)
+ yield put(actions.auth.loginRoutine(false, true))
yield put(actions.auth.registerSuccess())
} catch (e) {
yield put(actions.auth.registerFailure())
@@ -347,7 +338,7 @@ export default ({ api, coreSagas }) => {
yield put(actions.alerts.displayInfo(C.RESTORE_WALLET_INFO))
yield call(coreSagas.wallet.restoreWalletSaga, action.payload)
yield put(actions.alerts.displaySuccess(C.RESTORE_SUCCESS))
- yield call(loginRoutineSaga, false, true)
+ yield put(actions.auth.loginRoutine(false, true))
yield put(actions.auth.restoreSuccess())
} catch (e) {
yield put(actions.auth.restoreFailure())
diff --git a/packages/main-process/src/data/auth/sagas.spec.js b/packages/main-process/src/data/auth/sagas.spec.js
index a5dedcd54f8..3497b89edb9 100644
--- a/packages/main-process/src/data/auth/sagas.spec.js
+++ b/packages/main-process/src/data/auth/sagas.spec.js
@@ -48,7 +48,7 @@ describe('authSagas', () => {
})
describe('login flow', () => {
- const { login, loginRoutineSaga, pollingSession } = authSagas({
+ const { login, pollingSession } = authSagas({
api,
coreSagas
})
@@ -93,11 +93,11 @@ describe('authSagas', () => {
})
})
- it('should call login routine', () => {
+ it('should put login routine', () => {
const { mobileLogin } = payload
saga
.next()
- .call(loginRoutineSaga, mobileLogin)
+ .put(actions.auth.loginRoutine(mobileLogin))
.next()
.isDone()
})
@@ -185,9 +185,9 @@ describe('authSagas', () => {
})
})
- it('should call login routine', () => {
+ it('should put login routine', () => {
const { mobileLogin } = payload
- saga.next().call(loginRoutineSaga, mobileLogin)
+ saga.next().put(actions.auth.loginRoutine(mobileLogin))
})
it('should follow 2FA flow on auth error', () => {
@@ -352,7 +352,11 @@ describe('authSagas', () => {
})
const mobileLogin = true
const firstLogin = false
- const saga = testSaga(loginRoutineSaga, mobileLogin, firstLogin)
+
+ const saga = testSaga(loginRoutineSaga, {
+ payload: { mobileLogin, firstLogin }
+ })
+
const beforeHdCheck = 'beforeHdCheck'
it('should check if wallet is an hd wallet', () => {
@@ -369,13 +373,9 @@ describe('authSagas', () => {
.restore(beforeHdCheck)
})
- it('should put authenticate action', () => {
- saga.next(true).put(actions.auth.authenticate())
- })
-
it('should fetch root', () => {
saga
- .next()
+ .next(true)
.call(coreSagas.kvStore.root.fetchRoot, askSecondPasswordEnhancer)
})
@@ -444,14 +444,6 @@ describe('authSagas', () => {
saga.next(guid).put(actions.cache.guidEntered(guid))
})
- it('should reset auth state', () => {
- saga.next().put(actions.auth.setAuthType(0))
- })
-
- it('should clear login form', () => {
- saga.next().put(actions.form.destroy('login'))
- })
-
it('should select current language', () => {
saga.next().select(selectors.preferences.getLanguage)
})
@@ -492,7 +484,10 @@ describe('authSagas', () => {
it("should not display success if it's first login", () => {
const firstLogin = true
- return expectSaga(loginRoutineSaga, mobileLogin, firstLogin)
+
+ return expectSaga(loginRoutineSaga, {
+ payload: { mobileLogin, firstLogin }
+ })
.provide([
// Every async or value returning yield has to be mocked
// for saga to progress
@@ -527,7 +522,7 @@ describe('authSagas', () => {
})
describe('register flow', () => {
- const { loginRoutineSaga, register } = authSagas({
+ const { register } = authSagas({
api,
coreSagas
})
@@ -550,10 +545,10 @@ describe('authSagas', () => {
saga.next().put(actions.alerts.displaySuccess(C.REGISTER_SUCCESS))
})
- it('should call login routine saga with falsy mobileLogin and truthy firstLogin', () => {
+ it('should put login routine action with falsy mobileLogin and truthy firstLogin', () => {
const mobileLogin = false
const firstLogin = true
- saga.next().call(loginRoutineSaga, mobileLogin, firstLogin)
+ saga.next().put(actions.auth.loginRoutine(mobileLogin, firstLogin))
})
it('should finally trigger action that restore is successful', () => {
@@ -586,7 +581,7 @@ describe('authSagas', () => {
})
describe('restore flow', () => {
- const { loginRoutineSaga, restore } = authSagas({
+ const { restore } = authSagas({
api,
coreSagas
})
@@ -615,10 +610,10 @@ describe('authSagas', () => {
saga.next().put(actions.alerts.displaySuccess(C.RESTORE_SUCCESS))
})
- it('should call login routine saga with falsy mobileLogin and truthy firstLogin', () => {
+ it('should put login routine action with falsy mobileLogin and truthy firstLogin', () => {
const mobileLogin = false
const firstLogin = true
- saga.next().call(loginRoutineSaga, mobileLogin, firstLogin)
+ saga.next().put(actions.auth.loginRoutine(mobileLogin, firstLogin))
})
it('should finally trigger action that restore is successful', () => {
diff --git a/packages/main-process/src/data/components/importBtcAddress/sagaRegister.js b/packages/main-process/src/data/components/importBtcAddress/sagaRegister.js
index 1e44b52fd4a..c4fbfe71692 100644
--- a/packages/main-process/src/data/components/importBtcAddress/sagaRegister.js
+++ b/packages/main-process/src/data/components/importBtcAddress/sagaRegister.js
@@ -2,8 +2,8 @@ import { takeLatest } from 'redux-saga/effects'
import * as AT from './actionTypes'
import sagas from './sagas'
-export default ({ api, coreSagas, networks }) => {
- const importBtcAddressSagas = sagas({ api, coreSagas, networks })
+export default (...args) => {
+ const importBtcAddressSagas = sagas(...args)
return function * importBtcAddressSaga () {
yield takeLatest(
diff --git a/packages/main-process/src/data/components/sagaRegister.js b/packages/main-process/src/data/components/sagaRegister.js
index 3fb331706d6..bbc8f65793b 100644
--- a/packages/main-process/src/data/components/sagaRegister.js
+++ b/packages/main-process/src/data/components/sagaRegister.js
@@ -30,36 +30,36 @@ import transactionReport from './transactionReport/sagaRegister'
import uploadDocuments from './uploadDocuments/sagaRegister'
import veriff from './veriff/sagaRegister'
-export default ({ api, coreSagas, networks }) =>
+export default (...args) =>
function * componentsSaga () {
yield fork(activityList())
yield fork(bchTransactions())
yield fork(btcTransactions())
- yield fork(coinify({ api, coreSagas, networks }))
+ yield fork(coinify(...args))
yield fork(ethTransactions())
yield fork(xlmTransactions())
- yield fork(exchange({ api, coreSagas, networks }))
- yield fork(exchangeHistory({ api, coreSagas }))
- yield fork(identityVerification({ api, coreSagas }))
- yield fork(lockbox({ api, coreSagas }))
- yield fork(importBtcAddress({ api, coreSagas, networks }))
- yield fork(manageAddresses({ api, networks }))
+ yield fork(exchange(...args))
+ yield fork(exchangeHistory(...args))
+ yield fork(identityVerification(...args))
+ yield fork(lockbox(...args))
+ yield fork(importBtcAddress(...args))
+ yield fork(manageAddresses(...args))
yield fork(onboarding())
- yield fork(onfido({ api, coreSagas }))
- yield fork(priceChart({ coreSagas }))
- yield fork(priceTicker({ coreSagas }))
+ yield fork(onfido(...args))
+ yield fork(priceChart(...args))
+ yield fork(priceTicker(...args))
yield fork(refresh())
- yield fork(requestBtc({ networks }))
- yield fork(requestBch({ networks }))
- yield fork(requestEth({ networks }))
+ yield fork(requestBtc(...args))
+ yield fork(requestBch(...args))
+ yield fork(requestEth(...args))
yield fork(requestXlm())
- yield fork(sendBch({ coreSagas, networks }))
- yield fork(sendBtc({ coreSagas, networks }))
- yield fork(sendEth({ api, coreSagas, networks }))
- yield fork(sendXlm({ api, coreSagas }))
- yield fork(settings({ coreSagas }))
- yield fork(signMessage({ coreSagas }))
- yield fork(transactionReport({ coreSagas }))
- yield fork(uploadDocuments({ api }))
- yield fork(veriff({ api, coreSagas }))
+ yield fork(sendBch(...args))
+ yield fork(sendBtc(...args))
+ yield fork(sendEth(...args))
+ yield fork(sendXlm(...args))
+ yield fork(settings(...args))
+ yield fork(signMessage(...args))
+ yield fork(transactionReport(...args))
+ yield fork(uploadDocuments(...args))
+ yield fork(veriff(...args))
}
diff --git a/packages/main-process/src/data/components/sagas.js b/packages/main-process/src/data/components/sagas.js
index 76bb3c9021e..7ab7b7d8a10 100644
--- a/packages/main-process/src/data/components/sagas.js
+++ b/packages/main-process/src/data/components/sagas.js
@@ -28,7 +28,7 @@ import transactionReport from './transactionReport/sagas'
import uploadDocuments from './uploadDocuments/sagas'
import veriff from './veriff/sagas'
-export default ({ api, coreSagas, networks }) => ({
+export default ({ api, coreSagas, imports, networks }) => ({
activityList: activityList(),
bchTransactions: bchTransactions(),
btcTransactions: btcTransactions(),
@@ -38,7 +38,7 @@ export default ({ api, coreSagas, networks }) => ({
exchange: exchange({ api, coreSagas, networks }),
exchangeHistory: exchangeHistory({ api, coreSagas }),
identityVerification: identityVerification({ api, coreSagas }),
- importBtcAddress: importBtcAddress({ api, coreSagas, networks }),
+ importBtcAddress: importBtcAddress({ api, coreSagas, imports, networks }),
manageAddresses: manageAddresses({ api, networks }),
onboarding: onboarding(),
onfido: onfido({ api }),
diff --git a/packages/main-process/src/data/modules/sagaRegister.js b/packages/main-process/src/data/modules/sagaRegister.js
index d4d3986916f..47079cd578b 100644
--- a/packages/main-process/src/data/modules/sagaRegister.js
+++ b/packages/main-process/src/data/modules/sagaRegister.js
@@ -7,13 +7,13 @@ import securityCenter from './securityCenter/sagaRegister'
import transferEth from './transferEth/sagaRegister'
import sfox from './sfox/sagaRegister'
-export default ({ api, coreSagas, networks }) =>
+export default (...args) =>
function * modulesSaga () {
- yield fork(addressesBch({ coreSagas, networks }))
- yield fork(profile({ api, coreSagas, networks }))
- yield fork(rates({ api }))
- yield fork(settings({ api, coreSagas }))
- yield fork(securityCenter({ coreSagas }))
- yield fork(transferEth({ coreSagas, networks }))
- yield fork(sfox({ api, coreSagas, networks }))
+ yield fork(addressesBch(...args))
+ yield fork(profile(...args))
+ yield fork(rates(...args))
+ yield fork(settings(...args))
+ yield fork(securityCenter(...args))
+ yield fork(transferEth(...args))
+ yield fork(sfox(...args))
}
diff --git a/packages/main-process/src/data/modules/sagas.js b/packages/main-process/src/data/modules/sagas.js
index 00b1640f76d..ad3d01b8bbf 100644
--- a/packages/main-process/src/data/modules/sagas.js
+++ b/packages/main-process/src/data/modules/sagas.js
@@ -6,12 +6,12 @@ import securityCenter from './securityCenter/sagas'
import transferEth from './transferEth/sagas'
import sfox from './sfox/sagas'
-export default ({ api, coreSagas, networks }) => ({
- addressesBch: addressesBch({ coreSagas }),
- profile: profile({ api, coreSagas, networks }),
- rates: rates({ api }),
- settings: settings({ api, coreSagas }),
- securityCenter: securityCenter({ coreSagas }),
- transferEth: transferEth({ coreSagas, networks }),
- sfox: sfox({ api, coreSagas })
+export default (...args) => ({
+ addressesBch: addressesBch(...args),
+ profile: profile(...args),
+ rates: rates(...args),
+ settings: settings(...args),
+ securityCenter: securityCenter(...args),
+ transferEth: transferEth(...args),
+ sfox: sfox(...args)
})
diff --git a/packages/main-process/src/data/modules/settings/sagaRegister.js b/packages/main-process/src/data/modules/settings/sagaRegister.js
index 7c093289516..3bea440ef08 100644
--- a/packages/main-process/src/data/modules/settings/sagaRegister.js
+++ b/packages/main-process/src/data/modules/settings/sagaRegister.js
@@ -2,8 +2,8 @@ import { takeLatest } from 'redux-saga/effects'
import * as AT from './actionTypes'
import sagas from './sagas'
-export default ({ api, coreSagas }) => {
- const settingsSagas = sagas({ api, coreSagas })
+export default (...args) => {
+ const settingsSagas = sagas(...args)
return function * settingsModuleSaga () {
yield takeLatest(AT.INIT_SETTINGS_INFO, settingsSagas.initSettingsInfo)
@@ -11,7 +11,6 @@ export default ({ api, coreSagas }) => {
AT.INIT_SETTINGS_PREFERENCES,
settingsSagas.initSettingsPreferences
)
- yield takeLatest(AT.SHOW_BACKUP_RECOVERY, settingsSagas.showBackupRecovery)
yield takeLatest(
AT.SHOW_GOOGLE_AUTHENTICATOR_SECRET_URL,
settingsSagas.showGoogleAuthenticatorSecretUrl
diff --git a/packages/main-process/src/data/modules/settings/sagas.js b/packages/main-process/src/data/modules/settings/sagas.js
index 325a4145031..4b9f89dfcb6 100644
--- a/packages/main-process/src/data/modules/settings/sagas.js
+++ b/packages/main-process/src/data/modules/settings/sagas.js
@@ -3,7 +3,6 @@ import profileSagas from 'data/modules/profile/sagas'
import * as actions from '../../actions'
import * as selectors from '../../selectors'
import * as C from 'services/AlertService'
-import { addLanguageToUrl } from 'services/LocalesService'
import {
askSecondPasswordEnhancer,
promptForSecondPassword
@@ -19,7 +18,7 @@ export const ipRestrictionError =
export const logLocation = 'modules/settings/sagas'
-export default ({ api, coreSagas }) => {
+export default ({ api, coreSagas, imports, securityModule }) => {
const { syncUserWithWallet } = profileSagas({
api,
coreSagas
@@ -45,26 +44,6 @@ export default ({ api, coreSagas }) => {
}
}
- const recoverySaga = function * ({ password }) {
- const getMnemonic = s => selectors.core.wallet.getMnemonic(s, password)
- try {
- const mnemonicT = yield select(getMnemonic)
- const mnemonic = yield call(() => taskToPromise(mnemonicT))
- const mnemonicArray = mnemonic.split(' ')
- yield put(
- actions.modules.settings.addMnemonic({ mnemonic: mnemonicArray })
- )
- } catch (e) {
- yield put(
- actions.logs.logErrorMessage(logLocation, 'showBackupRecovery', e)
- )
- }
- }
-
- const showBackupRecovery = function * () {
- yield call(askSecondPasswordEnhancer(recoverySaga), {})
- }
-
const showGoogleAuthenticatorSecretUrl = function * () {
try {
const googleAuthenticatorSecretUrl = yield call(
@@ -142,7 +121,7 @@ export default ({ api, coreSagas }) => {
const updateLanguage = function * (action) {
try {
yield call(coreSagas.settings.setLanguage, action.payload)
- addLanguageToUrl(action.payload.language)
+ imports.addLanguageToUrl(action.payload.language)
} catch (e) {
yield put(actions.logs.logErrorMessage(logLocation, 'updateLanguage', e))
}
@@ -327,18 +306,18 @@ export default ({ api, coreSagas }) => {
try {
const password = yield call(promptForSecondPassword)
if (isLegacy) {
- const getSeedHex = state =>
- selectors.core.wallet.getSeedHex(state, password)
- const seedHexT = yield select(getSeedHex)
- const seedHex = yield call(() => taskToPromise(seedHexT))
- const legPriv = utils.eth.getLegacyPrivateKey(seedHex).toString('hex')
+ const legPriv = utils.eth
+ .getLegacyPrivateKey(securityModule)
+ .toString('hex')
yield put(actions.modules.settings.addShownEthPrivateKey(legPriv))
} else {
- const getMnemonic = state =>
- selectors.core.wallet.getMnemonic(state, password)
- const mnemonicT = yield select(getMnemonic)
- const mnemonic = yield call(() => taskToPromise(mnemonicT))
- let priv = utils.eth.getPrivateKey(mnemonic, 0).toString('hex')
+ let priv = (yield call(
+ utils.eth.getPrivateKey,
+ securityModule,
+ password,
+ 0
+ )).toString('hex')
+
yield put(actions.modules.settings.addShownEthPrivateKey(priv))
}
} catch (e) {
@@ -351,11 +330,7 @@ export default ({ api, coreSagas }) => {
const showXlmPrivateKey = function * () {
try {
const password = yield call(promptForSecondPassword)
- const getMnemonic = state =>
- selectors.core.wallet.getMnemonic(state, password)
- const mnemonicT = yield select(getMnemonic)
- const mnemonic = yield call(() => taskToPromise(mnemonicT))
- const keyPair = utils.xlm.getKeyPair(mnemonic)
+ const keyPair = yield call(utils.xlm.getKeyPair, securityModule, password)
yield put(
actions.modules.settings.addShownXlmPrivateKey(keyPair.secret())
)
@@ -369,7 +344,6 @@ export default ({ api, coreSagas }) => {
return {
initSettingsInfo,
initSettingsPreferences,
- showBackupRecovery,
showGoogleAuthenticatorSecretUrl,
updateMobile,
resendMobile,
@@ -387,7 +361,6 @@ export default ({ api, coreSagas }) => {
enableTwoStepGoogleAuthenticator,
enableTwoStepYubikey,
newHDAccount,
- recoverySaga,
showBtcPrivateKey,
showEthPrivateKey,
showXlmPrivateKey
diff --git a/packages/main-process/src/data/modules/settings/sagas.spec.js b/packages/main-process/src/data/modules/settings/sagas.spec.js
index 5f8393ffdee..b975a6cee0e 100644
--- a/packages/main-process/src/data/modules/settings/sagas.spec.js
+++ b/packages/main-process/src/data/modules/settings/sagas.spec.js
@@ -201,11 +201,6 @@ describe('settingsSagas', () => {
saga.next().call(coreSagas.settings.setLanguage, action.payload)
})
- it('should add the language to the url', () => {
- saga.next()
- expect(contains(action.payload.language, window.location.href)).toBe(true)
- })
-
describe('error handling', () => {
const error = new Error('ERROR')
it('should log the error', () => {
@@ -674,21 +669,4 @@ describe('settingsSagas', () => {
saga.next(MOCK_PASSWORD).select(selectors.core.wallet.getWallet)
})
})
-
- describe('showEthPrivateKey', () => {
- const getMnemonic = () => jest.fn()
- const { showEthPrivateKey } = settingsSagas({ coreSagas })
-
- let action = { payload: { isLegacy: false } }
-
- it('should get the mnemonic', () => {
- return expectSaga(showEthPrivateKey, action)
- .provide([
- [matchers.call.fn(promptForSecondPassword), 'password'],
- [select(getMnemonic), 'mnemonicT'],
- [matchers.call.fn(() => taskToPromise), 'mnemonic']
- ])
- .run()
- })
- })
})
diff --git a/packages/main-process/src/data/preferences/sagaRegister.js b/packages/main-process/src/data/preferences/sagaRegister.js
index 4810f281be6..2c1035ebbf0 100644
--- a/packages/main-process/src/data/preferences/sagaRegister.js
+++ b/packages/main-process/src/data/preferences/sagaRegister.js
@@ -2,8 +2,8 @@ import { takeLatest } from 'redux-saga/effects'
import * as AT from './actionTypes'
import sagas from './sagas'
-export default () => {
- const preferencesSagas = sagas()
+export default ({ imports }) => {
+ const preferencesSagas = sagas({ imports })
return function * preferencesSaga () {
yield takeLatest(AT.SET_LANGUAGE, preferencesSagas.setLanguage)
diff --git a/packages/main-process/src/data/preferences/sagas.js b/packages/main-process/src/data/preferences/sagas.js
index 611eb8d13c5..3b8de2a470b 100644
--- a/packages/main-process/src/data/preferences/sagas.js
+++ b/packages/main-process/src/data/preferences/sagas.js
@@ -3,13 +3,13 @@ import * as actions from '../actions.js'
import * as C from 'services/AlertService'
import { addLanguageToUrl } from 'services/LocalesService'
-export default () => {
+export default ({ imports }) => {
const logLocation = 'preferences/sagas'
const setLanguage = function * (action) {
const { language, showAlert } = action.payload
try {
- addLanguageToUrl(language)
+ imports.addLanguageToUrl(language)
if (showAlert) {
yield put(actions.alerts.displaySuccess(C.LANGUAGE_UPDATE_SUCCESS))
}
diff --git a/packages/main-process/src/data/rootSaga.js b/packages/main-process/src/data/rootSaga.js
index 49b086cfd5a..d75470860fc 100644
--- a/packages/main-process/src/data/rootSaga.js
+++ b/packages/main-process/src/data/rootSaga.js
@@ -1,6 +1,5 @@
-import { all, call, delay, fork, put } from 'redux-saga/effects'
+import { all, fork } from 'redux-saga/effects'
import { coreSagasFactory, coreRootSagaFactory } from 'blockchain-wallet-v4/src'
-import * as actions from './actions'
import alerts from './alerts/sagaRegister'
import analytics from './analytics/sagaRegister'
import auth from './auth/sagaRegister'
@@ -11,71 +10,21 @@ import preferences from './preferences/sagaRegister'
import goals from './goals/sagaRegister'
import router from './router/sagaRegister'
import wallet from './wallet/sagaRegister'
-import { tryParseLanguageFromUrl } from 'services/LocalesService'
-const logLocation = 'data/rootSaga'
-
-const welcomeSaga = function * () {
- try {
- const version = APP_VERSION
- const style1 = 'background: #F00; color: #FFF; font-size: 24px;'
- const style2 = 'font-size: 18px;'
- /* eslint-disable */
- console.log('=======================================================')
- console.log(`%c Wallet version ${version}`, style2)
- console.log('=======================================================')
- console.log('%c STOP!!', style1)
- console.log('%c This browser feature is intended for developers.', style2)
- console.log('%c If someone told you to copy-paste something here,', style2)
- console.log(
- '%c it is a scam and will give them access to your money!',
- style2
- )
- /* eslint-enable */
- } catch (e) {
- yield put(actions.logs.logErrorMessage(logLocation, 'welcomeSaga', e))
- }
-}
-
-const languageInitSaga = function * () {
- try {
- yield delay(250)
- const lang = tryParseLanguageFromUrl()
- if (lang.language) {
- yield put(actions.preferences.setLanguage(lang.language, false))
- if (lang.cultureCode) {
- yield put(actions.preferences.setCulture(lang.cultureCode))
- }
- }
- } catch (e) {
- yield put(actions.logs.logErrorMessage(logLocation, 'languageInitSaga', e))
- }
-}
-
-export default function * rootSaga ({
- api,
- bchSocket,
- btcSocket,
- ethSocket,
- ratesSocket,
- networks,
- options
-}) {
- const coreSagas = coreSagasFactory({ api, networks, options })
+export default function * rootSaga (args) {
+ const coreSagas = coreSagasFactory(args)
yield all([
- call(welcomeSaga),
fork(alerts),
- fork(analytics({ api })),
- fork(auth({ api, coreSagas })),
- fork(components({ api, coreSagas, networks, options })),
- fork(modules({ api, coreSagas, networks })),
- fork(preferences()),
- fork(goals({ api })),
- fork(wallet({ coreSagas })),
- fork(middleware({ api, bchSocket, btcSocket, ethSocket, ratesSocket })),
- fork(coreRootSagaFactory({ api, networks, options })),
- fork(router()),
- call(languageInitSaga)
+ fork(analytics({ ...args, coreSagas })),
+ fork(auth({ ...args, coreSagas })),
+ fork(components({ ...args, coreSagas })),
+ fork(modules({ ...args, coreSagas })),
+ fork(preferences({ ...args, coreSagas })),
+ fork(goals({ ...args, coreSagas })),
+ fork(wallet({ ...args, coreSagas })),
+ fork(middleware({ ...args, coreSagas })),
+ fork(coreRootSagaFactory({ ...args, coreSagas })),
+ fork(router({ ...args, coreSagas }))
])
}
diff --git a/packages/main-process/src/index.dev.js b/packages/main-process/src/index.dev.js
index 453f445412a..e99cc60c38a 100644
--- a/packages/main-process/src/index.dev.js
+++ b/packages/main-process/src/index.dev.js
@@ -8,21 +8,26 @@ import configureStore from 'store'
import App from 'scenes/app.js'
import Error from './index.error'
-const renderApp = (Component, store, history, persistor) => {
- const render = (Component, store, history, persistor) => {
+const renderApp = (Component, root) => {
+ const render = (Component, { imports, securityModule, store, history }) => {
ReactDOM.render(
-
+
,
document.getElementById('app')
)
}
- render(App, store, history, persistor)
+ render(App, root)
if (module.hot) {
module.hot.accept('./scenes/app.js', () =>
- render(require('./scenes/app.js').default, store, history, persistor)
+ render(require('./scenes/app.js').default, root)
)
}
}
@@ -35,7 +40,7 @@ const renderError = e => {
configureStore()
.then(root => {
- renderApp(App, root.store, root.history, root.persistor)
+ renderApp(App, root)
})
.catch(e => {
renderError(e)
diff --git a/packages/main-process/src/index.html b/packages/main-process/src/index.html
deleted file mode 100644
index 7858507df44..00000000000
--- a/packages/main-process/src/index.html
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Blockchain Wallet - Exchange Cryptocurrency
-
-
-
-
-
-
diff --git a/packages/main-process/src/index.prod.js b/packages/main-process/src/index.prod.js
index ba3478f7cd0..96967f535dc 100644
--- a/packages/main-process/src/index.prod.js
+++ b/packages/main-process/src/index.prod.js
@@ -1,14 +1,18 @@
import React from 'react'
import ReactDOM from 'react-dom'
-import './favicons'
import configureStore from 'store'
import App from 'scenes/app.js'
import Error from './index.error'
-const renderApp = (Component, store, history, persistor) => {
+const renderApp = (Component, { imports, securityModule, store, history }) => {
ReactDOM.render(
- ,
+ ,
document.getElementById('app')
)
}
@@ -19,7 +23,7 @@ const renderError = () => {
configureStore()
.then(root => {
- renderApp(App, root.store, root.history, root.persistor)
+ renderApp(App, root)
})
.catch(e => {
// eslint-disable-next-line no-console
diff --git a/packages/main-process/src/layouts/Wallet/Header/SecurityCenter/index.js b/packages/main-process/src/layouts/Wallet/Header/SecurityCenter/index.js
index 7ac41a91d31..0493f92c44a 100644
--- a/packages/main-process/src/layouts/Wallet/Header/SecurityCenter/index.js
+++ b/packages/main-process/src/layouts/Wallet/Header/SecurityCenter/index.js
@@ -7,11 +7,21 @@ import {
NavbarNavItemTextIcon
} from 'components/Navbar'
-const SecurityCenter = () => {
+const SecurityCenter = props => {
+ const onSecurityCenterClick = event => {
+ props.dispatch({
+ type: `ROOT_LOCATION_CHANGE`,
+ payload: { action: `PUSH`, location: { pathname: `/security-center` } }
+ })
+
+ event.preventDefault()
+ }
+
return (
diff --git a/packages/main-process/src/layouts/Wallet/Header/index.js b/packages/main-process/src/layouts/Wallet/Header/index.js
index eaebb156514..d93eedeee6d 100644
--- a/packages/main-process/src/layouts/Wallet/Header/index.js
+++ b/packages/main-process/src/layouts/Wallet/Header/index.js
@@ -18,7 +18,8 @@ class HeaderContainer extends React.PureComponent {
}
const mapDispatchToProps = dispatch => ({
- actions: bindActionCreators(actions.components.layoutWallet, dispatch)
+ actions: bindActionCreators(actions.components.layoutWallet, dispatch),
+ dispatch
})
export default withRouter(
diff --git a/packages/main-process/src/modals/Wallet/index.js b/packages/main-process/src/modals/Wallet/index.js
index 74785f29af5..32b26dacc44 100644
--- a/packages/main-process/src/modals/Wallet/index.js
+++ b/packages/main-process/src/modals/Wallet/index.js
@@ -1,5 +1,4 @@
import PairingCode from './PairingCode'
import ShowXPub from './ShowXPub'
-import UpgradeWallet from './UpgradeWallet'
-export { PairingCode, ShowXPub, UpgradeWallet }
+export { PairingCode, ShowXPub }
diff --git a/packages/main-process/src/modals/index.js b/packages/main-process/src/modals/index.js
index ac7e4a0b7d1..89c62ebc29a 100644
--- a/packages/main-process/src/modals/index.js
+++ b/packages/main-process/src/modals/index.js
@@ -73,7 +73,7 @@ import {
TwoStepSetup,
TwoStepYubico
} from './Settings'
-import { PairingCode, ShowXPub, UpgradeWallet } from './Wallet'
+import { PairingCode, ShowXPub } from './Wallet'
import {
RequestXlm,
SendXlm,
@@ -147,7 +147,6 @@ const Modals = () => (
-
diff --git a/packages/main-process/src/scenes/Settings/General/index.js b/packages/main-process/src/scenes/Settings/General/index.js
index cfae104eab7..af827217acf 100644
--- a/packages/main-process/src/scenes/Settings/General/index.js
+++ b/packages/main-process/src/scenes/Settings/General/index.js
@@ -1,14 +1,9 @@
import React from 'react'
import styled from 'styled-components'
-import { FormattedMessage } from 'react-intl'
-
-import { Banner, Text } from 'blockchain-info-components'
import About from './About'
-import PairingCode from './PairingCode'
import PrivacyPolicy from './PrivacyPolicy'
import TermsOfService from './TermsOfService'
-import WalletId from './WalletId'
const Wrapper = styled.section`
padding: 30px;
@@ -19,21 +14,6 @@ const Wrapper = styled.section`
const General = () => {
return (
-
-
-
-
-
-
-
-
-
diff --git a/packages/main-process/src/scenes/app.js b/packages/main-process/src/scenes/app.js
index 4b124f4cc36..443317c09f4 100644
--- a/packages/main-process/src/scenes/app.js
+++ b/packages/main-process/src/scenes/app.js
@@ -1,8 +1,7 @@
-import React from 'react'
+import React, { useEffect } from 'react'
import { Redirect, Switch } from 'react-router-dom'
import { connect, Provider } from 'react-redux'
import { ConnectedRouter } from 'connected-react-router'
-import { PersistGate } from 'redux-persist/integration/react'
import { map, values } from 'ramda'
import { createGlobalStyle } from 'styled-components'
@@ -56,22 +55,32 @@ const GlobalStyle = createGlobalStyle`
}
`
+const SetForegroundProcess = ({ children, imports }) => {
+ useEffect(() => {
+ imports.setForegroundProcess()
+ })
+
+ return {children}
+}
+
class App extends React.PureComponent {
render () {
const {
+ imports,
+ securityModule,
store,
history,
- persistor,
isAuthenticated,
supportedCoins
} = this.props
+
return (
-
-
-
-
+
+
+
+
@@ -159,14 +168,14 @@ class App extends React.PureComponent {
)}
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
)
diff --git a/packages/main-process/src/store/index.js b/packages/main-process/src/store/index.js
index 4e94ede5097..92df3976d09 100644
--- a/packages/main-process/src/store/index.js
+++ b/packages/main-process/src/store/index.js
@@ -1,11 +1,9 @@
-import { createStore, applyMiddleware, compose } from 'redux'
+import { combineReducers, createStore, applyMiddleware, compose } from 'redux'
+import { REHYDRATE } from 'redux-persist'
import createSagaMiddleware from 'redux-saga'
-import { persistStore, persistCombineReducers } from 'redux-persist'
-import storage from 'redux-persist/lib/storage'
-import getStoredStateMigrateV4 from 'redux-persist/lib/integration/getStoredStateMigrateV4'
import { createHashHistory } from 'history'
import { connectRouter, routerMiddleware } from 'connected-react-router'
-import { head } from 'ramda'
+import { dissoc, head, merge } from 'ramda'
import Bitcoin from 'bitcoinjs-lib'
import BitcoinCash from 'bitcoinforksjs-lib'
@@ -16,8 +14,11 @@ import {
ApiSocket,
HorizonStreamingService
} from 'blockchain-wallet-v4/src/network'
+import httpService from 'blockchain-wallet-v4/src/network/api/http'
+import Settings from 'blockchain-wallet-v4/src/network/api/settings'
import { serializer } from 'blockchain-wallet-v4/src/types'
import { actions, rootSaga, rootReducer, selectors } from 'data'
+import IPC from '../IPC'
import {
autoDisconnection,
streamingXlm,
@@ -29,6 +30,7 @@ import {
const devToolsConfig = {
maxAge: 1000,
+ name: `Main Process`,
serialize: serializer,
actionsBlacklist: [
// '@@redux-form/INITIALIZE',
@@ -43,7 +45,8 @@ const devToolsConfig = {
]
}
-const configureStore = () => {
+export default IPC(async ({ imports, middleware: IPCmiddleware }) => {
+ const { options } = imports
const history = createHashHistory()
const sagaMiddleware = createSagaMiddleware()
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
@@ -52,109 +55,109 @@ const configureStore = () => {
const walletPath = 'wallet.payload'
const kvStorePath = 'wallet.kvstore'
const isAuthenticated = selectors.auth.isAuthenticated
+ const apiKey = '1770d5d9-bcea-4d28-ad21-6cbd5be018a8'
+ // TODO: deprecate when wallet-options-v4 is updated on prod
+ const socketUrl = head(options.domains.webSocket.split('/inv'))
+ const horizonUrl = options.domains.horizon
+ const btcSocket = new Socket({
+ options,
+ url: `${socketUrl}/inv`
+ })
+ const bchSocket = new Socket({
+ options,
+ url: `${socketUrl}/bch/inv`
+ })
+ const ethSocket = new Socket({
+ options,
+ url: `${socketUrl}/eth/inv`
+ })
+ const ratesSocket = new ApiSocket({
+ options,
+ url: `${socketUrl}/nabu-gateway/markets/quotes`,
+ maxReconnects: 3
+ })
+ const xlmStreamingService = new HorizonStreamingService({
+ url: horizonUrl
+ })
- return fetch('/Resources/wallet-options-v4.json')
- .then(res => res.json())
- .then(options => {
- const apiKey = '1770d5d9-bcea-4d28-ad21-6cbd5be018a8'
- // TODO: deprecate when wallet-options-v4 is updated on prod
- const socketUrl = head(options.domains.webSocket.split('/inv'))
- const horizonUrl = options.domains.horizon
- const btcSocket = new Socket({
- options,
- url: `${socketUrl}/inv`
- })
- const bchSocket = new Socket({
- options,
- url: `${socketUrl}/bch/inv`
- })
- const ethSocket = new Socket({
- options,
- url: `${socketUrl}/eth/inv`
- })
- const ratesSocket = new ApiSocket({
- options,
- url: `${socketUrl}/nabu-gateway/markets/quotes`,
- maxReconnects: 3
- })
- const xlmStreamingService = new HorizonStreamingService({
- url: horizonUrl
- })
- const getAuthCredentials = () =>
- selectors.modules.profile.getAuthCredentials(store.getState())
- const reauthenticate = () =>
- store.dispatch(actions.modules.profile.signIn())
- const networks = {
- btc: Bitcoin.networks[options.platforms.web.coins.BTC.config.network],
- bch:
- BitcoinCash.networks[options.platforms.web.coins.BTC.config.network],
- eth: options.platforms.web.coins.ETH.config.network,
- xlm: options.platforms.web.coins.XLM.config.network
- }
- const api = createWalletApi({
- options,
- apiKey,
- getAuthCredentials,
- reauthenticate,
- networks
- })
- const persistWhitelist = ['session', 'preferences', 'cache']
+ const getAuthCredentials = () =>
+ selectors.modules.profile.getAuthCredentials(store.getState())
+ const reauthenticate = () => store.dispatch(actions.modules.profile.signIn())
+ const networks = {
+ btc: Bitcoin.networks[options.platforms.web.coins.BTC.config.network],
+ bch: BitcoinCash.networks[options.platforms.web.coins.BTC.config.network],
+ eth: options.platforms.web.coins.ETH.config.network,
+ xlm: options.platforms.web.coins.XLM.config.network
+ }
- // TODO: remove getStoredStateMigrateV4 someday (at least a year from now)
- const store = createStore(
- connectRouter(history)(
- persistCombineReducers(
- {
- getStoredState: getStoredStateMigrateV4({
- whitelist: persistWhitelist
- }),
- key: 'root',
- storage,
- whitelist: persistWhitelist
- },
- rootReducer
- )
- ),
- composeEnhancers(
- applyMiddleware(
- sagaMiddleware,
- routerMiddleware(history),
- coreMiddleware.kvStore({ isAuthenticated, api, kvStorePath }),
- webSocketBtc(btcSocket),
- webSocketBch(bchSocket),
- webSocketEth(ethSocket),
- streamingXlm(xlmStreamingService, api),
- webSocketRates(ratesSocket),
- coreMiddleware.walletSync({ isAuthenticated, api, walletPath }),
- autoDisconnection()
- )
- )
- )
- const persistor = persistStore(store, null)
+ const http = httpService({ apiKey, imports })
+ const securityModule = imports.securityProcess.securityModule
- sagaMiddleware.run(rootSaga, {
- api,
- bchSocket,
- btcSocket,
- ethSocket,
- ratesSocket,
- networks,
- options
- })
+ const baseApi = createWalletApi({
+ getAuthCredentials,
+ http,
+ imports,
+ options,
+ reauthenticate,
+ networks,
+ securityModule
+ })
- // expose globals here
- window.createTestXlmAccounts = () => {
- store.dispatch(actions.core.data.xlm.createTestAccounts())
- }
+ const rootUrl = options.domains.root
+ const api = { ...baseApi, ...Settings({ ...http, rootUrl, securityModule }) }
+ const combinedReducer = combineReducers(rootReducer)
- store.dispatch(actions.goals.defineGoals())
+ const reducer = (state, action) => {
+ const { payload, type } = action
- return {
- store,
- history,
- persistor
- }
- })
-}
+ return type === REHYDRATE
+ ? merge(state, dissoc(`_persist`, payload))
+ : combinedReducer(state, action)
+ }
+
+ // TODO: remove getStoredStateMigrateV4 someday (at least a year from now)
+ const store = createStore(
+ connectRouter(history)(reducer),
+ composeEnhancers(
+ applyMiddleware(
+ IPCmiddleware,
+ sagaMiddleware,
+ routerMiddleware(history),
+ coreMiddleware.kvStore({ isAuthenticated, api, kvStorePath }),
+ webSocketBtc(btcSocket),
+ webSocketBch(bchSocket),
+ webSocketEth(ethSocket),
+ streamingXlm(xlmStreamingService, api),
+ webSocketRates(ratesSocket),
+ coreMiddleware.walletSync({ isAuthenticated, mergeWrapper: true }),
+ autoDisconnection()
+ )
+ )
+ )
+
+ sagaMiddleware.run(rootSaga, {
+ api,
+ bchSocket,
+ btcSocket,
+ ethSocket,
+ imports,
+ ratesSocket,
+ networks,
+ options,
+ securityModule
+ })
+
+ // expose globals here
+ window.createTestXlmAccounts = () => {
+ store.dispatch(actions.core.data.xlm.createTestAccounts())
+ }
+
+ store.dispatch(actions.goals.defineGoals())
-export default configureStore
+ return {
+ imports,
+ securityModule,
+ store,
+ history
+ }
+})
diff --git a/packages/main-process/src/store/index.spec.js b/packages/main-process/src/store/index.spec.js
deleted file mode 100644
index 223a9549862..00000000000
--- a/packages/main-process/src/store/index.spec.js
+++ /dev/null
@@ -1,172 +0,0 @@
-import configureStore from './index'
-import * as Redux from 'redux'
-import * as Middleware from '../middleware'
-import {
- createWalletApi,
- ApiSocket,
- Socket
-} from 'blockchain-wallet-v4/src/network'
-import { persistStore } from 'redux-persist'
-import * as coreMiddleware from 'blockchain-wallet-v4/src/redux/middleware'
-// setup mocks
-jest.mock('redux-saga', () => () => ({
- run: () => jest.fn()
-}))
-
-jest.mock('redux-persist', () => ({
- persistCombineReducers: jest.fn(),
- persistStore: jest.fn()
-}))
-
-jest.mock('connected-react-router', () => ({
- connectRouter: () => () => jest.fn(),
- routerMiddleware: jest.fn()
-}))
-
-jest.mock('blockchain-wallet-v4/src/network', () => ({
- Socket: jest.fn().mockImplementation(() => 'FAKE_SOCKET'),
- ApiSocket: jest.fn().mockImplementation(() => 'FAKE_API_SOCKET'),
- createWalletApi: jest.fn().mockImplementation(() => 'FAKE_WALLET_API'),
- HorizonStreamingService: jest.fn()
-}))
-
-jest.mock('blockchain-wallet-v4/src/redux/middleware', () => ({
- kvStore: jest.fn(),
- walletSync: jest.fn()
-}))
-
-jest.mock('../middleware', () => ({
- webSocketBtc: jest.fn(),
- webSocketBch: jest.fn(),
- webSocketEth: jest.fn(),
- streamingXlm: jest.fn(),
- webSocketRates: jest.fn(),
- autoDisconnection: jest.fn()
-}))
-
-describe('App Store Config', () => {
- let apiKey = '1770d5d9-bcea-4d28-ad21-6cbd5be018a8'
- let fakeWalletOptions = {
- domains: { webSocket: 'MOCK_SOCKET', root: 'MOCK_ROOT' },
- platforms: {
- web: {
- coins: {
- BTC: { config: { network: 'bitcoin' } },
- ETH: { config: { network: 1 } },
- XLM: { config: { network: 'public' } }
- }
- }
- }
- }
- let mockNetworks = {
- bch: {
- bech32: 'bc',
- bip32: { private: 76066276, public: 76067358 },
- messagePrefix: '\u0018Bitcoin Signed Message:\n',
- pubKeyHash: 0,
- scriptHash: 5,
- wif: 128
- },
- btc: {
- bip32: { private: 76066276, public: 76067358 },
- messagePrefix: '\u0018Bitcoin Signed Message:\n',
- pubKeyHash: 0,
- scriptHash: 5,
- wif: 128
- },
- eth: 1
- }
- let createStoreSpy,
- applyMiddlewareSpy,
- composeSpy,
- kvStoreSpy,
- walletSyncSpy,
- autoDisconnectSpy,
- btcSocketSpy,
- bchSocketSpy,
- ethSocketSpy
-
- beforeAll(() => {
- // setup fetch mock
- fetch.resetMocks()
- fetch.mockResponseOnce(JSON.stringify(fakeWalletOptions))
-
- // setup spies
- createStoreSpy = jest.spyOn(Redux, 'createStore')
- applyMiddlewareSpy = jest.spyOn(Redux, 'applyMiddleware')
- composeSpy = jest.spyOn(Redux, 'compose').mockImplementation(jest.fn())
- kvStoreSpy = jest.spyOn(coreMiddleware, 'kvStore')
- walletSyncSpy = jest.spyOn(coreMiddleware, 'walletSync')
- btcSocketSpy = jest.spyOn(Middleware, 'webSocketBtc')
- bchSocketSpy = jest.spyOn(Middleware, 'webSocketBch')
- ethSocketSpy = jest.spyOn(Middleware, 'webSocketEth')
- autoDisconnectSpy = jest.spyOn(Middleware, 'autoDisconnection')
- })
-
- it('the entire app should bootstrap correctly', async () => {
- // bootstrap
- let mockStore = await configureStore()
-
- // assertions
- // wallet options
- expect(fetch.mock.calls.length).toEqual(1)
- expect(fetch.mock.calls[0][0]).toEqual('/Resources/wallet-options-v4.json')
- // socket registration
- expect(Socket.mock.calls.length).toEqual(3)
- expect(Socket.mock.calls[0][0]).toEqual({
- options: fakeWalletOptions,
- url: `${fakeWalletOptions.domains.webSocket}/inv`
- })
- expect(Socket.mock.calls[1][0]).toEqual({
- options: fakeWalletOptions,
- url: `${fakeWalletOptions.domains.webSocket}/bch/inv`
- })
- expect(Socket.mock.calls[2][0]).toEqual({
- options: fakeWalletOptions,
- url: `${fakeWalletOptions.domains.webSocket}/eth/inv`
- })
- expect(ApiSocket).toHaveBeenCalledTimes(1)
- expect(ApiSocket).toHaveBeenCalledWith({
- options: fakeWalletOptions,
- url: `${fakeWalletOptions.domains.webSocket}/nabu-gateway/markets/quotes`,
- maxReconnects: 3
- })
- // build api
- expect(createWalletApi.mock.calls.length).toBe(1)
- expect(createWalletApi.mock.calls[0][0]).toMatchObject({
- options: fakeWalletOptions,
- networks: mockNetworks,
- apiKey: apiKey
- })
- // middleware registration
- expect(kvStoreSpy).toHaveBeenCalledTimes(1)
- expect(kvStoreSpy).toHaveBeenCalledWith({
- isAuthenticated: expect.any(Function),
- api: 'FAKE_WALLET_API',
- kvStorePath: 'wallet.kvstore'
- })
- expect(btcSocketSpy).toHaveBeenCalledTimes(1)
- expect(btcSocketSpy).toHaveBeenCalledWith(expect.any(Object))
- expect(bchSocketSpy).toHaveBeenCalledTimes(1)
- expect(bchSocketSpy).toHaveBeenCalledWith(expect.any(Object))
- expect(ethSocketSpy).toHaveBeenCalledTimes(1)
- expect(ethSocketSpy).toHaveBeenCalledWith(expect.any(Object))
- expect(walletSyncSpy).toHaveBeenCalledTimes(1)
- expect(walletSyncSpy).toHaveBeenCalledWith({
- isAuthenticated: expect.any(Function),
- api: 'FAKE_WALLET_API',
- walletPath: 'wallet.payload'
- })
- expect(autoDisconnectSpy).toHaveBeenCalledTimes(1)
- // middleware compose
- expect(composeSpy).toHaveBeenCalledTimes(1)
- expect(applyMiddlewareSpy).toHaveBeenCalledTimes(1)
- // store creation
- expect(createStoreSpy).toHaveBeenCalledTimes(1)
- expect(persistStore.mock.calls.length).toBe(1)
- expect(persistStore.mock.calls[0][0]).toEqual(expect.any(Object))
- expect(persistStore.mock.calls[0][1]).toEqual(null)
- expect(mockStore.history).toBeDefined()
- expect(mockStore.store).toBeDefined()
- })
-})
diff --git a/packages/main-process/src/template.html b/packages/main-process/src/template.html
new file mode 100644
index 00000000000..7aa7e721812
--- /dev/null
+++ b/packages/main-process/src/template.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/main-process/webpack.config.ci.js b/packages/main-process/webpack.config.ci.js
new file mode 100644
index 00000000000..81cb14cf4a2
--- /dev/null
+++ b/packages/main-process/webpack.config.ci.js
@@ -0,0 +1,80 @@
+/* eslint-disable */
+const babelConfig = require(`./babel.config.js`)
+const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
+ .BundleAnalyzerPlugin
+const CleanWebpackPlugin = require('clean-webpack-plugin')
+const HtmlWebpackPlugin = require('html-webpack-plugin')
+const path = require(`path`)
+const Webpack = require('webpack')
+
+const runBundleAnalyzer = process.env.ANALYZE
+const src = path.join(__dirname, `src`)
+
+module.exports = ({ envConfig, PATHS }) => ({
+ name: `main`,
+ entry: ['@babel/polyfill', path.join(src, 'index.js')],
+ output: {
+ path: PATHS.ciBuild,
+ chunkFilename: '[name].[chunkhash:10].js',
+ publicPath: '/main/',
+ crossOriginLoading: 'anonymous'
+ },
+ module: {
+ rules: [
+ {
+ test: /\.js$/,
+ use: [
+ { loader: 'thread-loader', options: { workerParallelJobs: 50 } },
+ { loader: 'babel-loader', options: babelConfig }
+ ]
+ },
+ {
+ test: /\.(eot|ttf|otf|woff|woff2)$/,
+ use: {
+ loader: 'file-loader',
+ options: {
+ name: 'fonts/[name]-[hash].[ext]'
+ }
+ }
+ },
+ {
+ test: /\.(png|jpg|gif|svg|ico|webmanifest|xml)$/,
+ use: {
+ loader: 'file-loader',
+ options: {
+ name: 'img/[name].[ext]'
+ }
+ }
+ },
+ {
+ test: /\.(pdf)$/,
+ use: {
+ loader: 'file-loader',
+ options: {
+ name: 'resources/[name]-[hash].[ext]'
+ }
+ }
+ },
+ {
+ test: /\.css$/,
+ use: [{ loader: 'style-loader' }, { loader: 'css-loader' }]
+ }
+ ]
+ },
+ plugins: [
+ new CleanWebpackPlugin(),
+ new Webpack.DefinePlugin({
+ APP_VERSION: JSON.stringify(require(PATHS.pkgJson).version),
+ NETWORK_TYPE: JSON.stringify(envConfig.NETWORK_TYPE)
+ }),
+ new HtmlWebpackPlugin({
+ template: path.join(src, 'template.html'),
+ filename: 'index.html'
+ }),
+ new Webpack.IgnorePlugin({
+ resourceRegExp: /^\.\/locale$/,
+ contextRegExp: /moment$/
+ }),
+ ...(runBundleAnalyzer ? [new BundleAnalyzerPlugin({})] : [])
+ ]
+})
diff --git a/packages/main-process/webpack.config.dev.js b/packages/main-process/webpack.config.dev.js
new file mode 100644
index 00000000000..dc033c2b086
--- /dev/null
+++ b/packages/main-process/webpack.config.dev.js
@@ -0,0 +1,86 @@
+/* eslint-disable */
+const babelConfig = require(`./babel.config.js`)
+const CleanWebpackPlugin = require('clean-webpack-plugin')
+const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin')
+const HtmlWebpackPlugin = require('html-webpack-plugin')
+const path = require(`path`)
+const Webpack = require('webpack')
+
+const src = path.join(__dirname, `src`)
+
+module.exports = ({ envConfig, localhostUrl, PATHS }) => ({
+ name: `main`,
+ entry: [
+ '@babel/polyfill',
+ 'react-hot-loader/patch',
+ `webpack-dev-server/client?${localhostUrl}`,
+ 'webpack/hot/only-dev-server',
+ path.join(src, 'index.js')
+ ],
+ output: {
+ path: PATHS.appBuild,
+ chunkFilename: '[name].[chunkhash:10].js',
+ publicPath: localhostUrl + `/main/`,
+ crossOriginLoading: 'anonymous'
+ },
+ module: {
+ rules: [
+ {
+ test: /\.js$/,
+ include: /src|blockchain-info-components.src|blockchain-wallet-v4.src/,
+ use: [
+ { loader: 'thread-loader', options: { workerParallelJobs: 50 } },
+ { loader: 'babel-loader', options: babelConfig }
+ ]
+ },
+ {
+ test: /\.(eot|ttf|otf|woff|woff2)$/,
+ use: {
+ loader: 'file-loader',
+ options: {
+ name: 'fonts/[name]-[hash].[ext]'
+ }
+ }
+ },
+ {
+ test: /\.(png|jpg|gif|svg|ico|webmanifest|xml)$/,
+ use: {
+ loader: 'file-loader',
+ options: {
+ name: 'img/[name].[ext]'
+ }
+ }
+ },
+ {
+ test: /\.(pdf)$/,
+ use: {
+ loader: 'file-loader',
+ options: {
+ name: 'resources/[name]-[hash].[ext]'
+ }
+ }
+ },
+ {
+ test: /\.css$/,
+ use: [{ loader: 'style-loader' }, { loader: 'css-loader' }]
+ }
+ ]
+ },
+ plugins: [
+ new CleanWebpackPlugin(),
+ new CaseSensitivePathsPlugin(),
+ new Webpack.DefinePlugin({
+ APP_VERSION: JSON.stringify(require(PATHS.pkgJson).version),
+ NETWORK_TYPE: JSON.stringify(envConfig.NETWORK_TYPE)
+ }),
+ new HtmlWebpackPlugin({
+ template: path.join(src, 'template.html'),
+ filename: 'index.html'
+ }),
+ new Webpack.IgnorePlugin({
+ resourceRegExp: /^\.\/locale$/,
+ contextRegExp: /moment$/
+ }),
+ new Webpack.HotModuleReplacementPlugin()
+ ]
+})
diff --git a/packages/main-process/webpack.debug.js b/packages/main-process/webpack.debug.js
new file mode 100644
index 00000000000..3ef5f8b1813
--- /dev/null
+++ b/packages/main-process/webpack.debug.js
@@ -0,0 +1,78 @@
+/* eslint-disable */
+const babelConfig = require(`./babel.config.js`)
+const CleanWebpackPlugin = require('clean-webpack-plugin')
+const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin')
+const HtmlWebpackPlugin = require('html-webpack-plugin')
+const Webpack = require('webpack')
+const path = require('path')
+
+const src = path.join(__dirname, `src`)
+
+module.exports = ({ envConfig, localhostUrl, PATHS }) => ({
+ name: `main`,
+ entry: ['@babel/polyfill', path.join(src, 'index.js')],
+ output: {
+ path: PATHS.ciBuild,
+ chunkFilename: '[name].[chunkhash:10].js',
+ publicPath: localhostUrl + `/main/`,
+ crossOriginLoading: 'anonymous'
+ },
+ module: {
+ rules: [
+ {
+ test: /\.js$/,
+ use: [
+ { loader: 'thread-loader', options: { workerParallelJobs: 50 } },
+ { loader: 'babel-loader', options: babelConfig }
+ ]
+ },
+ {
+ test: /\.(eot|ttf|otf|woff|woff2)$/,
+ use: {
+ loader: 'file-loader',
+ options: {
+ name: 'fonts/[name]-[hash].[ext]'
+ }
+ }
+ },
+ {
+ test: /\.(png|jpg|gif|svg|ico|webmanifest|xml)$/,
+ use: {
+ loader: 'file-loader',
+ options: {
+ name: 'img/[name].[ext]'
+ }
+ }
+ },
+ {
+ test: /\.(pdf)$/,
+ use: {
+ loader: 'file-loader',
+ options: {
+ name: 'resources/[name]-[hash].[ext]'
+ }
+ }
+ },
+ {
+ test: /\.css$/,
+ use: [{ loader: 'style-loader' }, { loader: 'css-loader' }]
+ }
+ ]
+ },
+ plugins: [
+ new CleanWebpackPlugin(),
+ new CaseSensitivePathsPlugin(),
+ new Webpack.DefinePlugin({
+ APP_VERSION: JSON.stringify(require(PATHS.pkgJson).version),
+ NETWORK_TYPE: JSON.stringify(envConfig.NETWORK_TYPE)
+ }),
+ new HtmlWebpackPlugin({
+ template: path.join(src, 'template.html'),
+ filename: 'index.html'
+ }),
+ new Webpack.IgnorePlugin({
+ resourceRegExp: /^\.\/locale$/,
+ contextRegExp: /moment$/
+ })
+ ]
+})
diff --git a/packages/root-process/babel.config.js b/packages/root-process/babel.config.js
new file mode 100644
index 00000000000..16bedc9a2f6
--- /dev/null
+++ b/packages/root-process/babel.config.js
@@ -0,0 +1,8 @@
+module.exports = {
+ presets: [['@babel/preset-env', { modules: false }]],
+ plugins: [
+ '@babel/plugin-proposal-class-properties',
+ '@babel/plugin-proposal-object-rest-spread',
+ 'babel-plugin-styled-components'
+ ]
+}
diff --git a/packages/root-process/package.json b/packages/root-process/package.json
new file mode 100644
index 00000000000..9a189776c97
--- /dev/null
+++ b/packages/root-process/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "root-process",
+ "version": "1.0.0",
+ "main": "index.js",
+ "license": "MIT",
+ "scripts": {
+ "test": "echo 'No tests to execute.'",
+ "test:build": "echo 'No precomplilation required for tests to execute.'"
+ },
+ "devDependencies": {
+ "eslint": "5.16.0",
+ "webpack": "4.31.0",
+ "webpack-cli": "3.3.2",
+ "webpack-dev-server": "3.4.0"
+ },
+ "dependencies": {
+ "axios": "0.19.0",
+ "web-microkernel": "1.0.0"
+ }
+}
diff --git a/packages/root-process/src/assets/images/favicon/android-chrome-192x192.png b/packages/root-process/src/assets/images/favicon/android-chrome-192x192.png
new file mode 100644
index 0000000000000000000000000000000000000000..4a892a3cdd9de192019f4da3e08727056e95000b
GIT binary patch
literal 4868
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
literal 0
HcmV?d00001
diff --git a/packages/root-process/src/assets/images/favicon/android-chrome-512x512.png b/packages/root-process/src/assets/images/favicon/android-chrome-512x512.png
new file mode 100644
index 0000000000000000000000000000000000000000..27eb4cdfc77fba9f1884d3b9b4b47f9c23a6fb05
GIT binary patch
literal 13373
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
literal 0
HcmV?d00001
diff --git a/packages/root-process/src/assets/images/favicon/apple-touch-icon.png b/packages/root-process/src/assets/images/favicon/apple-touch-icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..b19393522885b83a30093199815e9b80454a4f66
GIT binary patch
literal 2421
zcmZ`(dpMN&7k^u+EnU_sR;iyJ&!%>%F|Sy<7t7>65u*~}Ww3*o6*7(7Ds8P?GD9xI
zh{)|7iZCui$vd){ahb-J%HYMg48zPc{AQnL|M~s#JJ0!k&U3!c`JD4S=Q-!xadx!X
zx_Q@T006e)Y_0Gr%lhYitER%uT2d7P>jO?ZoCbiJ2O3{})K$K|uPxpI0Alt705uT+
z~1OSjR0DyV}fMqrS=!E4qx*P+5Z%Ul)U9H#F*3dagN58P}Wp{c_TXJdRjRF><
z=?#ZBGCjYjRIa`m9=%u59GzGD$FmkhFsfW#<$WCgqoSEVIu5_?XLa+IR%E%)yYd=d
zEz9JK%d)hpR;f&$QPcLYx;3M=UAiLAtZmPP+q2+~6}jROtK$)ilg;AD;D(Xxugim?L
zCl_}*TSE#r0YH#Ji$;F)byxRE3HPpwoC%vdC#T;sG&CHyHnp17)
z)=15*K5_Zx&7A6FCWn$!6_wXc`lCHN+Q)2pj%*YB6Ort|y9=jz&e
z-5&C7LpkFM%U_jBGxwMa>HOyRlf`g{dj@}0ymZ7px>+zO9G&n;=SyUY%#sE|246TW
z@g%1aGWp%3Gv~rO588)&W(+h8P5f{IT30E$lsN$NMHwY8aFqNC?q|VAkz_>yx?OJ)
zOcqqPeiSdUTDdq%foOi&uTa1koR%q+g@e-tgVX1zjQqiA9F>u$!gz-7BNkI27R!~`
zmMP;V6kSmoHKAb3)Xz=mL?%75k184cqm+n#FclscpOmG5f~
za*_`>WT3&EcLu%*-x|XeK_t$#8l2nKD_&bkNoh5NE>Cw?>t{Ue-B93|n`^l7Obt;g
zO5|^V@kQ@kpVN`8taSJu!L(Hxc=6;&i`3J;(w9H+P?$bjYZATo<
zf36dtH4Rdc6>p}f0>PEO58-YqegEsloJ>!HN~pyMjlb=k#X~-Y}HmzwN1b
zXiLodT>oClrb3>U0wJL%A~581U2lzGNmlgQ{*ax2hp-2@B7OAE#g5i`ADZOG#SiE=
zVC(Nja1^;v*E=?A7;fZjsaETmb~{eAIS_1^W$`xZ?;o3Vz1_2ha|D4KhMv7^ip@*8
z;GVrs>P>4dHJsVxMvHpw73~&lSH20N#(6;DN4i7s=aH>TEw@D7iZldJ9L?RGH$V^B
z@Jm784gJ52O6xIddK_$O*)sqR|<^O?FIzsqVU6n!|m|35**y`?9RcSd2c*HmG>
zSeXC#5JWYQBQ}-=(Lpv~tBVmB{Ojy7!Cza#?4Qd#Y{0Wz%yQ`nw>Q+u2y6{Ef{W-d+vA;Ho*KG$eP2(-lC_R|;gi%H
z&BH#lx)xUZ4cbQV)0m%Rr#kek%9LK!C`i$r-<3tU5-ijol$jWKy)_ArOV|`wWC%K3
z4jKTrh;o`}nxRm3T?9;L#OS4{FiVgVbu^5VTx1dW4rUK2%19ov^f-K~u
zDu_d=0~uV?UX!R1*D?`G%v}|?fd(h+
zOw+xBZy&b51b&Jzl}OfuE?o0|o)w5Q_9-dthT0XZd%E-L77NU3Ql-$r8c+0d8Xr1@$Y6jH)cm)9rNH~w&NAp
z?XEDQ*5VV>nbSbvnwpoz(G}&xBFcv`*%5=5p8{5|%)A$tYnvQ)R+ulBCLtr};5XSX
zc~?cr@+EzUWBvspNJSS^^-o8Lef%cpsJL}bNLNU36-$07+Dv9ZMV5q!vr1VX7%;4JMVrpPYyQNUzDACh<%Ige
zPvs;ru&{iRvtN_G_PL4jC78zUR?@<%M$NT?w~m418Cm
zy2C$4T6;wL`9%6#`d;%_832nh`NbGxYHVWeiZ!uRDG2_I!CGQ4y+qkb1`RfzIB7FhidhX|iE#HP;IB?*NQxAe0
z!vkst8i#x}v<$TD(s!t7>041vJN(oPU|0Q~A>L~a!qMYw?4jBgoI3=dI+~FSXO3j5
PN&q-(N2|&+-nae>jad
+
+
+
+
+ #2d89ef
+
+
+
diff --git a/packages/root-process/src/assets/images/favicon/favicon-16x16.png b/packages/root-process/src/assets/images/favicon/favicon-16x16.png
new file mode 100644
index 0000000000000000000000000000000000000000..34b445a571253c553832f31064a3378314becc89
GIT binary patch
literal 798
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLl<6e(pbstU$g(vPY0F
z14ES>14Ba#1H&(%P{RubhEf9thF1v;3|2E37{m+a>VBS&fig
zQ+M6ncLUU>>+Oy6@SV&`KZJLBjxAi3$%bI!_D4C|ix?0Lel;Tgl)
zXAJ9}Gpv6O6alfFdh=h(%CTRGmqG%XgsABu^K|5Q)pa{oz8*20W~;oSYNl
zJlC^a&N=a>#M8^9%%)RV`_juZ8-r4U*4{iL
z-Ze=edv6Tm8uMva%hHejn{aTVjVMV;EJ?LW
zE=mPb3`Pcq7P>t*I;7bhncr0V4trO$q6BL!5%1yU4}S(1~=kdvRD?>|kl2q+p}
z6%tVrlvu7%P?VpRnUkteQdy9ykXcZY%)n4F=kX^Vj>0evjZ^-o&v-r!VqjM0)=TCV
zRu=Z2EW#|T;L>1nIE7hxbBMy}8&^&oIdeqj2>a;|wWF{uCm
literal 0
HcmV?d00001
diff --git a/packages/root-process/src/assets/images/favicon/favicon-32x32.png b/packages/root-process/src/assets/images/favicon/favicon-32x32.png
new file mode 100644
index 0000000000000000000000000000000000000000..664c498f8187e374fdde95b111fc8791bc100a41
GIT binary patch
literal 1079
zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyEa{HEjtmSN`?>!lvVtU&J%W50
z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+0810!pIPl)UP|NqNpuBw{9
zv2xy~s@Y(2-lmd8>ww(KdE0;_k8Pr7ZV!+VRJBbzbia^gl1E;5Q1w>zkX;^my+Ku5
zJ#xDPtG2o4cI!m$5x0s3DhRCHsu8kFIdG?#c?yv0meUnbwM{*Amu=!9AQy-@ioA~6^1A)S!$DhaO2fyKq;%V13)Rx;>A{J
z2RVy?o-?d_212V|F>HIwu>L8O
zyZ%1VFF*r`*-;8zr$A~_^|R#%iNl(a5DXKEMgqu8B7r$SG~t#8W;^$%R#e)82;n|zbQC1=}SJ0BKdwz^bQ
zGo^niYi{?)X^-dltFX=$Q3}?0UgITN64l`|(c-zWNonMntogH_+vvV%x)2@HRa@Em
zIP5x0Uaj-bWe;<*tJ#~+Cj-7mWccc4s
zw*%`KHtYz~YyKkihK-^7w)N9bLci2}*>}`;#NJE!G~
zF!(Crw--f2ZhlH;S|x4`Ew0}fff^)1HU#IVm6RtIr81P4m+NKbWfvzW7NqLs7p2dB
zXCnnv#063ml39|I%8-+vp6@?RvIr;|UKJ8i5|mi3P*9YgmYI{PP*Pcts*qVwlFYzR
zG3W6o9*)8=4UJR&r_Xpk4Pszc=GIH*7FHJao-D#Ftl-jMayW%qd2@)u=^Iy09657D
i<_P=g29E_^dJM0`1xr3TnN9^-!QkoY=d#Wzp$PzrZN?n{
literal 0
HcmV?d00001
diff --git a/packages/root-process/src/assets/images/favicon/favicon.ico b/packages/root-process/src/assets/images/favicon/favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..775e9c6cbbf4a93f6aa4f89835653a597b4899ae
GIT binary patch
literal 15086
zcmd6uYitx%6vr==1R9NfU|YY@1eF^7Kw=_nDJ}Mc0!1T)8u(yhTLQ$0K`GL%CM;l-
zh{hO;1T5Q^7+mO`$5nqbi0b0kStBAU3WZZcF*>9Iy*bBZ9B>B
zna4fn{QmdedF-7jV~bb{TfLfr&1L7GV(bORSZ;1~JDss#VP85}Jid1cV;@6>Jg5UM
z>`f@RUtFGym}LJAG_x1STUgt;lLf>A>%Ti$akBFojW5{PV&MJ)n8Q#6Y4JN*jo^UJ
zSbz(%2b|0n4Qk=dC*`QJ85vT
zhPmp8B(T%@kaP`6WG4;v$?AtWU>7y`n^~RN8s>_f?saBrm}_>@fcJ8v{V-qbW@xZ6
zws_aN%v$G%%LXXkTwDLu%>#ZiqBsd(g-*M%Zj2ezlj#;|TR1L$QWb}@Fclf51Gr@KZgQYBgd9g-z(h|B*
zWxOANy{B{gswpUwBj=CYO290h%T#B~+ge<)HnUA9Kk5iM3~2C7vkSnFMU#ecPb;zq
z4DjpLAj7Ul1F{?C7d3oewX9d|In9k3VwPI9{SYuXe&}3Kt-*6Vqc1w%rOeXpgc@-C
z7&w1JHdm;_u^%MCu-oNCSrTb=R
z7(SkUQzyHu2Dy8%4WBtJ8Ht`Bq?}6Iq6T39$soH#L;Ppas0P{RH$B(bV`cdR9q+$_
z>}oX}t0)+Vo~!YgrTar22R18RmrP?%qygFC{G5uOr&3O(?Ws5#
zOlOa$!2mnyl4?jAyHvwRy9)KTJF%0UYCo_E$5PVSc@3_!hiY|<8DgGkA(q;E6EPUI
zHk)m6Yh6{HVGR%iV;SYqK)L1NzPvJL$#zE_+pjRt@Mg-|W1G*0a@(sx?5}
zg~C2VgT1OLv4*Oqg~m=A9FEcyS6y{YJPmjZ>rnlLYFq79#h{@LN+4Qby-NJ8Z8ozY
zgo*@#F+q5c7O|sCBi5Y~v9?7Ki*=xc89R(N#8?s9Nyb*7eZW{6+EK=qqIENtg4PD#
zAE8BX_yg_`fH81~0E~e<1Yiu@Apm2eXqPhPMhiVL<^+BB9qb8ZZ(~Ola?^YIq%o+-
zoiqjwxlL!#k=rx|1Gx<|n2=jHgNeB{GnB1=G^=5M>Uo*-n_}s+?j0BId)pMR4{zk>
zWW&6U2THhZe`>dB4Bb0>;N4UhiZ33+t~J>?kk`x2D~0RLV3^KL@5p8`M01aDxoQ43
z-1x46c9@-e7Q^o6GtwZhPbPPaq3J#C48E^cy2plE{-Jk*GS+wZmIHDxKc$dc7sKr%
zkK?<`uzPoYKQKhUm56+Az;ldyU6@<#d$MrTfz+NvW0G>yoapu4`F-%MNjwIv@9Dzl
z6d0tri2>Rn9s}f8)&A5EX#YD`IlCR={G)twR%B3{n`gLp+c_+~4#`}9kK2hF*O
zg=aXCIQKNe2QdcSxp{`q_H8;J9cz>$k%hk4c2R4aQ%!HQ_#LMYrMc>?
zr+`76$H8^nN5{2TahhgwMP!-=rxI{lAv(o?Qx7-|fm0GVJuwPX6>gZeu!%DvGqd16
zJT?k-urB(bZ*2MkV`6N~fm0Qj6Q(L~o&qNha-qcLDVD&7%4NKn6~g~k4T=TURl14Q
z0xfI3&D4S?tw}%?p@yT
zBlny;F+So#-$C*vD;MKuYmU3CH1Cf)S5DpKVxaFVe~iP&KwrA|K#V`{bbkFyG{&Hm
zpBN|x?@KW{x99zJxAWu>@?$$j+wURoi|b!jzUpqby&lUw?Z4__*N(gmd>@pr$tVDy
zAjLpmx|T>{1iMO~F5rD>onr95k{EG)DaO$~MHcW0k{@S-9hDnrj2rTiTJR}?GQs)c
zy5iWE#&FdhDg>WFEVwQ`PTS$bFt-blCs)8+E6(S_oU0q>abZrD7F@@Dcns@cUGzcU
F{{ioqUfBQu
literal 0
HcmV?d00001
diff --git a/packages/root-process/src/assets/images/favicon/mstile-144x144.png b/packages/root-process/src/assets/images/favicon/mstile-144x144.png
new file mode 100644
index 0000000000000000000000000000000000000000..f3c4f925b28752157c7d42d018955f7c1f1b66f5
GIT binary patch
literal 2488
zcmcIkcT`hp7JnL;AePV~EMh=m2`VBWjEIR2kWge4!z7duK?qeqQE3S}Ah1Z0VF6J}
z2$Dggjsy~llqfFXupqrlk&;FYHG&QH19s2Z-E;QOJ?Fmr{qFC(_jm7m?|U!d!g;IR
zvZ}HW1nsu5#@U1QuN#*P4EXj;g+#DO`IwzEgCKUw&adv$Al4vT+n<9Vsx|~g#X`_$
zKt)YJP>4PRO%tJV@})%A!6hq2zI>bPna8#-`-5!|9x;
zf^uqJ355=f6S~2$0oY25%B!K|Z87D5yqUF`mbK4XeN~8as?uV&GD#L#ztRWvg2SqKQ>H1a26u7)`2Dw+sl)
zVWWwa+Rim7d;lQBXdHt}6(9$Mp*h7`q;in9ALk#MQ>0C-(j-*wx3~g$U_ej}=O32K
z(RJp6T?5w&^iNf47FUjtYQQc~*k|Dvnq9Qd+%F`j2n-XN4a7?fT`N@0{6exf7*#Xh
z&4F2}KWs3nxIle!wW;@;@>UVxH|&%rjNT>ncwF0&d))Q)i&h@HN4OON4-)>}Z-v^9
z)$DG5&B(}B$nF*bP~5`@{$GrM-9NpBIo*OS9>``jHv_EgP|ev)2FhEtn_wgDzW~VG
zAPdIWnip`tX634Q+fMc
zHdEGpVm4ssZSi6pLh6U4zYQ!8)u&mm-NzHb4Q}(WwZaXEVcO9kfCt;0!^6j=gkxry(ZhxP3B$69(wY4U9xq+1*~sZ<
zDo^|G!Yr?o2M#vdDlD@;HG2HFs
zW}74?F9*eoDE}3W1XZj(!y9l3D>G#;{hLm2XE>ykeGxZEq&kc!2-j}$^{eaFgzSa^
z*d9LB13!tP=>Tyv_`8S~SpEeT+VcMF4n|!CO0>r(aS_vtN_)1^@h;X!-a&YUUT*l2e3m7YqKu@-
zet-piLb{}^_%WOij>MIMgmEn7Xo&kp5AC4+zJb&6F?5I2dw2CzKN=;mVqX{D0*)hmFw0%S4rxOQ7((%))a~>zwIF)-sI|lhd2@v
z1`TV@Y^zW{a8?o?>6SLM9#sn0Y?@z%tDi+ZSdk(mix$>W`2=ai8?rII;S3#AHm1=qQ!EU7{U~zj)fMdLDaDfn0yK
zmaINZcU;EIGR#vVjVb3kVDq2%8Kp#~o0|I4z2$2aE#ZL`_E@vh_|azwNBeJ&BI58W
zZHu4HbbQqiZ!RVk)7PToii$CIa<7t?bsuHiVlnm1DRE~{i&g4ZAnuZ0ZCALyg_;r>
z1IWtVoV3a7mMUSyO~9xv`d
zUdLn~q;Gsaz6qaDTz{*@}Izibc
z!UMlZH%W~{TNd!fl1ecysr0+(&gCFn6{^a5&;mIb;ma_@C=pJgxKDKv4n#T}eYh(|
zJ_DS@jM+m98FP)wq_IXt60dPDNw9-)tP^82Tihjc-9bmE$?jdVjldp)Y3|e`?^M)O
zv`trnDQe)NjOsjLnk7Vycm3UeuWznbtB1dfvVL?{51LYz4_-7k&HybS8~k}3%lvZm
FKLA25t!4lK
literal 0
HcmV?d00001
diff --git a/packages/root-process/src/assets/images/favicon/mstile-150x150.png b/packages/root-process/src/assets/images/favicon/mstile-150x150.png
new file mode 100644
index 0000000000000000000000000000000000000000..6326cd191c61c0540b8e08d90e01f177f02e9714
GIT binary patch
literal 2502
zcmbtUd00~E9zKmNwvbjS{na5VrIdcr83Zq!1yfkh6Twdl87Wee9_e2tu|&Q0zqr
zngvqqFa({!LePj81eqs6(0Y1ug%bgSey}~bk7ffB&ZQMF(~6j>#Sy7sEn=ns0Im|x
z-Od3@RGP{{l1l(TckgQsa8rxU-YsFK6$7&4z*%N$A;2F0C#&}y2O#*cfbMYm>2_L$
zp35`90kJsF+U{Iwar{?v?+fr!EMEY|Gk
zd0_o?GzN6maensGkr>br?-7Idhz6U)9gYHna2`=09}v?+QJ~h;JrZ!^Ly>?RyCe2r
zEvy?8>xO_M0Be^UVt@Ln9kU*A_2@j*?_FR7DYUjgEGu{^Es)3x+hxE4Kyf-z2_2+R
zF0fSOBzGmd1L$Ba?vQ@V0h|=V1tmGc-ftG@Rwz+*09HZAe@KyJ5GWeg+DRvCzmX#G
zS0%o^@SCL~s$|XIR5^eHq8TqCh16=qx39d`0YFr3pnYXRr*&BE&y|B92dqSZDj5K&
zQU(-arOdig#Q{J!aGZsSd?;@aU`wg&8~OiN@NGeJa8l3;44t2!kK_H>4*sM{26ewP
zWQbIEf2xciH)<5?b{UN9e5XIWLvLuCbihDwh_4ik7|y&W#w1h(uLi+NxT0TXZ$L
znyb4siiL_|vVh#!)M(gPgES!3{Oa!d?oYhupXzuoc)a@B`udvs8Gb`UW0OK45H?F?
z+lR(?>6vVmyw*qkRkr$PUbFqqh|2Csc~4E$F4Er@-hy*1IPPdie&Ops5ekAO3+{Yay2XFnQoym&4U=x31Yo^w;;dma)
zg4erbL-O14)Wk%NZG~aa^#;qvO?hAX4$xaBUftF?SHxaSQr~h>^E=D^kK0a`Dh75q
zZwo7w>4+2E+a!S^vN-EbYCT$+>dKZewFpG$N_VN
z6T&`dIf<*4C*94xjtKFVz4Lj-yYvH&d7*jmede`410J9;Stm*yFghA**>r;KU~T|e
zG^b5irx)}9y?kG5P@=6mdNpd^(ig6dG&rIozLIXUoYG3DEbTs=v&yC?Bp4a-eiHFa
zmy&IJTDj>g&T_f{cje}&PWu&UN|oF_pI8##;8SHgQkeVhRa_>qdW1Dx
zc7uxAo&Pilu0jXFMsGOTL7JOts5$*_J_+>wQ0Z%29q~s#{T`s1Vm2-!I2^@H(T52E
z?TxEZ>CP(4d~;%uW(snL#!czluz!89X3D878fMB&AkX5yG*c#@=GScuZn5Z-=W$>fvR*`86=K@$A5TBZdl8jgCoK{r)*&2oSm^dB@<=(68_Gcd&qnqI
zbd8=mLqQD&O9&M>DkqpSkkrF%ur8><_4QhK
zgG!Whm4jQSSInt>6mIQZ2G7pyYW+pNBM)}9s@-Avw*Z&qiFR%)m|Wl~o3YDzG1ugt
zeL0iAR~E+#XG^5*t5F^rOB^`~xdGUk4x^5O4>{=rPGHhhX$E`=?SfxsIohH30es>)
z+a4$O>$J!30|t<;{OeMu11v=|NV<(agHP%UvdFpq(S#PwUZLTJTTEbpZoQ{y851_Y
zSi;e0qdgIVL4sHL_>n;eVd`l?}yY1)D=Nkg5m
iZe!gNL9G3~UyPuU^{W_@B>W990&=j~PtGHG{rYc9Td0Bn
literal 0
HcmV?d00001
diff --git a/packages/root-process/src/assets/images/favicon/mstile-310x150.png b/packages/root-process/src/assets/images/favicon/mstile-310x150.png
new file mode 100644
index 0000000000000000000000000000000000000000..43794bc7ac6b041f61765d4723026bfdf283986c
GIT binary patch
literal 2678
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
literal 0
HcmV?d00001
diff --git a/packages/root-process/src/assets/images/favicon/safari-pinned-tab.svg b/packages/root-process/src/assets/images/favicon/safari-pinned-tab.svg
new file mode 100644
index 00000000000..daecb91da10
--- /dev/null
+++ b/packages/root-process/src/assets/images/favicon/safari-pinned-tab.svg
@@ -0,0 +1,18 @@
+
+
+
diff --git a/packages/root-process/src/assets/images/favicon/site.webmanifest b/packages/root-process/src/assets/images/favicon/site.webmanifest
new file mode 100644
index 00000000000..1007f33d605
--- /dev/null
+++ b/packages/root-process/src/assets/images/favicon/site.webmanifest
@@ -0,0 +1,26 @@
+{
+ "name": "Blockchain Wallet",
+ "short_name": "Blockchain",
+ "icons": [
+ {
+ "src": "/img/android-chrome-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "/img/android-chrome-512x512.png",
+ "sizes": "512x512",
+ "type": "image/png"
+ }
+ ],
+ "theme_color": "#ffffff",
+ "background_color": "#ffffff",
+ "display": "standalone",
+ "prefer_related_applications": true,
+ "related_applications": [
+ {
+ "platform": "play",
+ "id": "piuk.blockchain.android"
+ }
+ ]
+}
diff --git a/packages/root-process/src/favicons.js b/packages/root-process/src/favicons.js
new file mode 100644
index 00000000000..91eb9fac4b6
--- /dev/null
+++ b/packages/root-process/src/favicons.js
@@ -0,0 +1,14 @@
+require('./assets/images/favicon/favicon.ico')
+require('./assets/images/favicon/favicon-16x16.png')
+require('./assets/images/favicon/favicon-32x32.png')
+require('./assets/images/favicon/android-chrome-192x192.png')
+require('./assets/images/favicon/android-chrome-512x512.png')
+require('./assets/images/favicon/apple-touch-icon.png')
+require('./assets/images/favicon/mstile-70x70.png')
+require('./assets/images/favicon/mstile-144x144.png')
+require('./assets/images/favicon/mstile-150x150.png')
+require('./assets/images/favicon/mstile-310x310.png')
+require('./assets/images/favicon/mstile-310x150.png')
+require('./assets/images/favicon/safari-pinned-tab.svg')
+require('./assets/images/favicon/site.webmanifest')
+require('./assets/images/favicon/browserconfig.xml')
diff --git a/packages/root-process/src/index.js b/packages/root-process/src/index.js
new file mode 100644
index 00000000000..aac8c8c960c
--- /dev/null
+++ b/packages/root-process/src/index.js
@@ -0,0 +1,203 @@
+import axios from 'axios'
+import * as kernel from 'web-microkernel'
+
+import './favicons'
+
+const LOCATION_CHANGE = `@@router/LOCATION_CHANGE`
+
+const securityProcessPaths = [
+ `/authorize-approve`,
+ `/help`,
+ `/login`,
+ `/logout`,
+ `/mobile-login`,
+ `/recover`,
+ `/reminder`,
+ `/reset-2fa`,
+ `/reset-two-factor`,
+ `/security-center`,
+ `/signup`,
+ `/verify-email`
+]
+
+const pathnameIsInSecurityProcess = pathname =>
+ securityProcessPaths.some(path => pathname.startsWith(path))
+
+;(async () => {
+ const rootProcess = kernel.RootProcess()
+ rootProcess.addEventListener(`error`, console.error)
+ const { createProcess, setForeground } = rootProcess
+ const optionsPromise = fetch('/Resources/wallet-options-v4.json')
+
+ const mainProcessPromise = createProcess({
+ name: `main`,
+ sandbox: `allow-forms allow-popups allow-popups-to-escape-sandbox allow-scripts`,
+ src: MAIN_DOMAIN + `/index.html#/login`
+ })
+
+ const pathname = window.location.hash.slice(1)
+
+ const securityProcess = await createProcess({
+ name: `security`,
+ // `allow-popups allow-popups-to-escape-sandbox`: Allow downloading of the
+ // backup phrase recovery PDF.
+ sandbox: `allow-forms allow-popups allow-popups-to-escape-sandbox allow-scripts`,
+ src: SECURITY_DOMAIN + `/index.html#${pathname}`
+ })
+
+ setForeground(securityProcess, `lightgreen`)
+
+ const replaceUrl = url => {
+ const data = {}
+ const title = ``
+ window.history.replaceState(data, title, url)
+ }
+
+ // update url with new language without forcing browser reload
+ const addLanguageToUrl = language => {
+ replaceUrl(`/${language}/${window.location.hash}`)
+ }
+
+ const sanitizedAxios = kernel.sanitizeFunction(axios)
+
+ const localStorageProxy = {
+ getItem: key => localStorage.getItem(key),
+ setItem: (key, value) => localStorage.setItem(key, value),
+ removeItem: key => localStorage.removeItem(key)
+ }
+
+ const logout = () => {
+ // router will fallback to /login route
+ replaceUrl(`#`)
+ window.location.reload(true)
+ }
+
+ let mainProcessExports
+ const mainProcessActions = []
+
+ const processMainActionsQueue = () => {
+ if (mainProcessExports) {
+ while (mainProcessActions.length > 0) {
+ const action = mainProcessActions.shift()
+ mainProcessExports.dispatch(action)
+ }
+ }
+ }
+
+ const mainProcessDispatch = action => {
+ mainProcessActions.push(action)
+ processMainActionsQueue()
+ }
+
+ const options = await (await optionsPromise).json()
+
+ const setForegroundProcess = () => {
+ const pathname = window.location.hash.slice(1)
+
+ if (pathnameIsInSecurityProcess(pathname)) {
+ setForeground(securityProcess)
+ } else {
+ setForeground(mainProcess)
+ }
+ }
+
+ const replaceFragment = identifier => {
+ const [withoutFragment] = window.location.href.split(`#`)
+ replaceUrl(`${withoutFragment}#${identifier}`)
+ }
+
+ // We remember the most recent Main Process location so we can revert the
+ // address bar to it when the back button is pressed in the Security Center.
+ let mainProcessPathname = `/login`
+
+ const dispatchFromSecurityProcess = action => {
+ if (action.type === LOCATION_CHANGE) {
+ const { pathname } = action.payload.location
+
+ if (pathnameIsInSecurityProcess(pathname)) {
+ replaceFragment(pathname)
+ } else {
+ // If the Security Center is parking at /home then show the most recent
+ // location in the Main Process.
+ if (pathname === `/home`) {
+ replaceFragment(mainProcessPathname)
+ setForegroundProcess()
+
+ // Now that the Security Process is in the background it's safe to
+ // park it.
+ securityProcessExports.dispatch(action)
+ } else {
+ replaceFragment(pathname)
+ mainProcessDispatch(action)
+ }
+ }
+ }
+ }
+
+ const securityProcessExports = await securityProcess({
+ addLanguageToUrl,
+ axios: sanitizedAxios,
+ localStorage: localStorageProxy,
+ logout,
+ mainProcessDispatch,
+ options,
+ pathname: window.location.pathname,
+ rootProcessDispatch: dispatchFromSecurityProcess,
+ setForegroundProcess
+ })
+
+ const dispatchFromMainProcess = action => {
+ if (action.type === LOCATION_CHANGE) {
+ const { pathname } = action.payload.location
+
+ // If mainProcessPathname is `/login` then the user just logged in and we
+ // want the Security Process to park itself at '/home' while the Main
+ // Process has the focus.
+ if (
+ pathnameIsInSecurityProcess(pathname) ||
+ mainProcessPathname === `/login`
+ ) {
+ securityProcessExports.dispatch(action)
+ }
+
+ // The Main Process doesn't have a /security-center route.
+ if (pathname !== `/security-center`) {
+ mainProcessPathname = pathname
+ }
+
+ replaceFragment(pathname)
+ }
+ }
+
+ const mainProcess = await mainProcessPromise
+
+ mainProcessExports = await mainProcess({
+ addLanguageToUrl,
+ axios: sanitizedAxios,
+ options,
+ pathname: window.location.pathname,
+ rootProcessDispatch: dispatchFromMainProcess,
+ securityProcess: securityProcessExports,
+ setForegroundProcess
+ })
+
+ processMainActionsQueue()
+
+ window.addEventListener(`popstate`, () => {
+ const pathname = window.location.hash.slice(1)
+
+ const action = {
+ type: LOCATION_CHANGE,
+ meta: { forwarded: true },
+ payload: { action: `PUSH`, location: { hash: ``, pathname, search: `` } }
+ }
+
+ if (pathnameIsInSecurityProcess(pathname)) {
+ securityProcessExports.dispatch(action)
+ } else {
+ mainProcessDispatch(action)
+ }
+
+ setForegroundProcess()
+ })
+})().catch(console.error)
diff --git a/packages/root-process/src/template.html b/packages/root-process/src/template.html
new file mode 100644
index 00000000000..227a56e5a4b
--- /dev/null
+++ b/packages/root-process/src/template.html
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Blockchain Wallet - Exchange Cryptocurrency
+
+
+
+
+
+
diff --git a/packages/root-process/webpack.config.ci.js b/packages/root-process/webpack.config.ci.js
new file mode 100644
index 00000000000..867aa79232d
--- /dev/null
+++ b/packages/root-process/webpack.config.ci.js
@@ -0,0 +1,49 @@
+'use strict'
+const babelConfig = require(`./babel.config.js`)
+const CleanWebpackPlugin = require('clean-webpack-plugin')
+const HtmlWebpackPlugin = require('html-webpack-plugin')
+const path = require(`path`)
+const Webpack = require('webpack')
+
+const src = path.join(__dirname, `src`)
+
+module.exports = ({ PATHS }) => ({
+ name: `root`,
+ entry: ['@babel/polyfill', path.join(src, 'index.js')],
+ module: {
+ rules: [
+ {
+ test: /\.js$/,
+ use: [
+ { loader: 'thread-loader', options: { workerParallelJobs: 50 } },
+ { loader: 'babel-loader', options: babelConfig }
+ ]
+ },
+ {
+ test: /\.(png|jpg|gif|svg|ico|webmanifest|xml)$/,
+ use: {
+ loader: 'file-loader',
+ options: {
+ name: 'img/[name].[ext]'
+ }
+ }
+ }
+ ]
+ },
+ output: {
+ filename: `index.js`,
+ path: PATHS.ciBuild,
+ publicPath: '/'
+ },
+ plugins: [
+ new CleanWebpackPlugin(),
+ new HtmlWebpackPlugin({
+ template: path.join(src, 'template.html'),
+ filename: 'index.html'
+ }),
+ new Webpack.DefinePlugin({
+ MAIN_DOMAIN: `"/main"`,
+ SECURITY_DOMAIN: `"/security"`
+ })
+ ]
+})
diff --git a/packages/root-process/webpack.config.dev.js b/packages/root-process/webpack.config.dev.js
new file mode 100644
index 00000000000..2f03a859288
--- /dev/null
+++ b/packages/root-process/webpack.config.dev.js
@@ -0,0 +1,48 @@
+'use strict'
+
+const CleanWebpackPlugin = require('clean-webpack-plugin')
+const HtmlWebpackPlugin = require('html-webpack-plugin')
+const path = require(`path`)
+const Webpack = require('webpack')
+
+const src = path.join(__dirname, `src`)
+
+module.exports = ({ localhostUrl, mainDomain, PATHS, securityDomain }) => ({
+ name: `root`,
+ entry: [
+ 'react-hot-loader/patch',
+ `webpack-dev-server/client?${localhostUrl}`,
+ 'webpack/hot/only-dev-server',
+ path.join(src, 'index.js')
+ ],
+ mode: `development`,
+ module: {
+ rules: [
+ {
+ test: /\.(png|jpg|gif|svg|ico|webmanifest|xml)$/,
+ use: {
+ loader: 'file-loader',
+ options: {
+ name: 'img/[name].[ext]'
+ }
+ }
+ }
+ ]
+ },
+ output: {
+ filename: `index.js`,
+ path: PATHS.appBuild
+ },
+ plugins: [
+ new CleanWebpackPlugin(),
+ new HtmlWebpackPlugin({
+ template: path.join(src, 'template.html'),
+ filename: 'index.html'
+ }),
+ new Webpack.DefinePlugin({
+ MAIN_DOMAIN: `"http://${mainDomain}"`,
+ SECURITY_DOMAIN: `"http://${securityDomain}"`
+ }),
+ new Webpack.HotModuleReplacementPlugin()
+ ]
+})
diff --git a/packages/root-process/webpack.debug.js b/packages/root-process/webpack.debug.js
new file mode 100644
index 00000000000..d074216c37a
--- /dev/null
+++ b/packages/root-process/webpack.debug.js
@@ -0,0 +1,48 @@
+'use strict'
+const babelConfig = require(`./babel.config.js`)
+const CleanWebpackPlugin = require('clean-webpack-plugin')
+const HtmlWebpackPlugin = require('html-webpack-plugin')
+const path = require(`path`)
+const Webpack = require('webpack')
+
+const src = path.join(__dirname, `src`)
+
+module.exports = ({ localhostUrl, mainDomain, PATHS, securityDomain }) => ({
+ name: `root`,
+ entry: ['@babel/polyfill', path.join(src, 'index.js')],
+ module: {
+ rules: [
+ {
+ test: /\.js$/,
+ use: [
+ { loader: 'thread-loader', options: { workerParallelJobs: 50 } },
+ { loader: 'babel-loader', options: babelConfig }
+ ]
+ },
+ {
+ test: /\.(png|jpg|gif|svg|ico|webmanifest|xml)$/,
+ use: {
+ loader: 'file-loader',
+ options: {
+ name: 'img/[name].[ext]'
+ }
+ }
+ }
+ ]
+ },
+ output: {
+ filename: `index.js`,
+ path: PATHS.ciBuild
+ },
+ plugins: [
+ new CleanWebpackPlugin(),
+ new HtmlWebpackPlugin({
+ template: path.join(src, 'template.html'),
+ filename: 'index.html'
+ }),
+ new Webpack.DefinePlugin({
+ MAIN_DOMAIN: `"http://${mainDomain}"`,
+ SECURITY_DOMAIN: `"http://${securityDomain}"`
+ })
+ ]
+})
diff --git a/packages/security-process/babel.config.js b/packages/security-process/babel.config.js
index 7b2631c7211..e339b510b49 100644
--- a/packages/security-process/babel.config.js
+++ b/packages/security-process/babel.config.js
@@ -1,11 +1,21 @@
+const path = require(`path`)
+
+const resolve = directory => path.resolve(__dirname, directory)
+
module.exports = {
presets: ['@babel/preset-env', '@babel/preset-react'],
plugins: [
'@babel/plugin-proposal-class-properties',
'@babel/plugin-proposal-object-rest-spread',
'babel-plugin-styled-components',
- ['module-resolver', { root: ['./src'], alias: { data: './src/data' } }],
- ['react-intl', { messagesDir: './build/extractedMessages' }]
+ [
+ 'module-resolver',
+ {
+ root: [resolve('src')],
+ alias: { data: resolve('src/data') }
+ }
+ ],
+ ['react-intl', { messagesDir: resolve('build/extractedMessages') }]
],
ignore: [],
env: {
@@ -18,8 +28,14 @@ module.exports = {
'@babel/plugin-proposal-class-properties',
'@babel/plugin-proposal-object-rest-spread',
'babel-plugin-styled-components',
- ['module-resolver', { root: ['./src'], alias: { data: './src/data' } }],
- ['react-intl', { messagesDir: './build/extractedMessages' }]
+ [
+ 'module-resolver',
+ {
+ root: [resolve('src')],
+ alias: { data: resolve('src/data') }
+ }
+ ],
+ ['react-intl', { messagesDir: resolve('build/extractedMessages') }]
]
},
development: {
@@ -31,7 +47,13 @@ module.exports = {
'@babel/plugin-proposal-class-properties',
'@babel/plugin-proposal-object-rest-spread',
'babel-plugin-styled-components',
- ['module-resolver', { root: ['./src'], alias: { data: './src/data' } }],
+ [
+ 'module-resolver',
+ {
+ root: [resolve('src')],
+ alias: { data: resolve('src/data') }
+ }
+ ],
'react-hot-loader/babel'
]
}
diff --git a/packages/security-process/package.json b/packages/security-process/package.json
index d304a44ea17..d4f2253ce0e 100644
--- a/packages/security-process/package.json
+++ b/packages/security-process/package.json
@@ -1,5 +1,5 @@
{
- "name": "blockchain-wallet-v4-frontend",
+ "name": "security-process",
"version": "0.1.0",
"description": "Frontend wallet application.",
"license": "AGPL-3.0-or-later",
@@ -19,13 +19,8 @@
"ci:compile": "cross-env-shell NODE_ENV=production webpack-cli --config webpack.config.ci.js --display-error-details",
"clean": "cross-env rimraf node_modules && rimraf build",
"coverage": "cross-env ./../../node_modules/.bin/jest --coverage",
- "debug:prod": "cross-env-shell NODE_ENV=production webpack-dev-server --config webpack.debug.js --progress --colors",
"link:resolved:paths": "ln -sf $(pwd)/src/** ./node_modules && ln -sf $(pwd)/../../packages/** ./node_modules",
"manage:translations": "yarn build:prod && node ./translationRunner.js",
- "start:dev": "cross-env-shell NODE_ENV=development webpack-dev-server --config webpack.config.dev.js --progress --colors --watch --devtool cheap-module-source-map",
- "start:prod": "cross-env-shell DISABLE_SSL=true NODE_ENV=production webpack-dev-server --config webpack.config.dev.js --progress --colors --watch --devtool cheap-module-source-map",
- "start:staging": "cross-env-shell NODE_ENV=staging webpack-dev-server --config webpack.config.dev.js --progress --colors --watch --devtool cheap-module-source-map",
- "start:testnet": "cross-env-shell NODE_ENV=testnet webpack-dev-server --config webpack.config.dev.js --progress --colors --watch --devtool cheap-module-source-map",
"test": "cross-env ./../../node_modules/.bin/jest --silent",
"test:build": "echo 'No precomplilation required for tests to execute.'",
"test:debug": "cross-env node --inspect-brk ./../../node_modules/.bin/jest --runInBand",
@@ -143,6 +138,7 @@
"rxjs": "6.5.2",
"sanitize-html": "1.20.1",
"styled-components": "4.2.0",
+ "web-microkernel": "1.0.0",
"zxcvbn": "4.4.2"
}
}
diff --git a/packages/security-process/src/IPC/Middleware.js b/packages/security-process/src/IPC/Middleware.js
new file mode 100644
index 00000000000..ee9ee7b7cc4
--- /dev/null
+++ b/packages/security-process/src/IPC/Middleware.js
@@ -0,0 +1,71 @@
+import * as router from 'connected-react-router'
+import { REHYDRATE } from 'redux-persist'
+
+import * as coreTypes from 'blockchain-wallet-v4/src/redux/actionTypes'
+import * as Wrapper from 'blockchain-wallet-v4/src/types/Wrapper'
+import * as types from '../data/actionTypes'
+
+const alreadyForwarded = ({ meta }) => meta && meta.forwarded
+
+const dispatchToMainProcess = ({ mainProcessDispatch }, action) => {
+ mainProcessDispatch(action)
+}
+
+const dispatchToRootProcess = ({ rootProcessDispatch }, action) => {
+ rootProcessDispatch(action)
+}
+
+const ROOT_LOCATION_CHANGE = ({ rootProcessDispatch }, { payload }) => {
+ rootProcessDispatch({ type: router.LOCATION_CHANGE, payload })
+}
+
+const handlers = {
+ // Dispatched by createRoot which requires the seed.
+ [coreTypes.kvStore.root.UPDATE_METADATA_ROOT]: dispatchToMainProcess,
+
+ // Dispatched by createXlm which requires the seed.
+ [coreTypes.kvStore.xlm.CREATE_METADATA_XLM]: dispatchToMainProcess,
+
+ // Report failure of wallet synchronization.
+ [coreTypes.walletSync.SYNC_ERROR]: dispatchToMainProcess,
+
+ // Report success of wallet synchronization.
+ [coreTypes.walletSync.SYNC_SUCCESS]: dispatchToMainProcess,
+
+ // We handle persistence for the Main Process.
+ [REHYDRATE]: dispatchToMainProcess,
+
+ // Report a location change to the Root Process instead of processing it
+ // ourselves.
+ ROOT_LOCATION_CHANGE,
+
+ // Inform the Root Process about routing changes so that it can switch the
+ // appropriate process to the foreground.
+ [router.LOCATION_CHANGE]: dispatchToRootProcess,
+
+ // Proceed with the login routine after receiving the payload.
+ [types.auth.AUTHENTICATE]: dispatchToMainProcess
+}
+
+// Used to set the wrapper in /recover.
+handlers[coreTypes.wallet.REFRESH_WRAPPER] = (
+ { mainProcessDispatch },
+ action
+) => {
+ const redactedWrapper = Wrapper.redact(action.payload)
+ mainProcessDispatch({ ...action, payload: redactedWrapper })
+}
+
+// Send the wrapper to the Main Process after logging in.
+handlers[coreTypes.wallet.SET_WRAPPER] =
+ handlers[coreTypes.wallet.REFRESH_WRAPPER]
+
+export default ({ imports }) => () => next => action => {
+ const { type } = action
+
+ if (!alreadyForwarded(action) && type in handlers) {
+ handlers[type](imports, action)
+ }
+
+ return next(action)
+}
diff --git a/packages/security-process/src/IPC/index.js b/packages/security-process/src/IPC/index.js
new file mode 100644
index 00000000000..f047575c961
--- /dev/null
+++ b/packages/security-process/src/IPC/index.js
@@ -0,0 +1,28 @@
+import { serializer } from 'blockchain-wallet-v4/src/types'
+import * as kernel from 'web-microkernel'
+
+import Middleware from './Middleware'
+
+export default configureStore => () =>
+ new Promise(async resolve => {
+ const exportedFunction = async imports => {
+ const middleware = Middleware({ imports })
+ const root = await configureStore({ imports, middleware })
+ resolve(root)
+
+ const dispatch = action =>
+ root.store.dispatch({
+ ...action,
+ meta: { ...action.meta, forwarded: true }
+ })
+
+ return { dispatch, securityModule: root.securityModule }
+ }
+
+ const childProcess = await kernel.ChildProcess(
+ { reviver: serializer.reviver },
+ exportedFunction
+ )
+
+ childProcess.addEventListener(`error`, console.error)
+ })
diff --git a/packages/security-process/src/data/actionTypes.js b/packages/security-process/src/data/actionTypes.js
index 0b00bb13607..12924bf15e1 100644
--- a/packages/security-process/src/data/actionTypes.js
+++ b/packages/security-process/src/data/actionTypes.js
@@ -3,11 +3,8 @@ import * as alerts from './alerts/actionTypes'
import * as analytics from './analytics/actionTypes'
import * as auth from './auth/actionTypes'
import * as cache from './cache/actionTypes'
-import * as components from './components/actionTypes'
import { actionTypes as form } from './form/actionTypes'
-import * as goals from './goals/actionTypes'
import * as logs from './logs/actionTypes'
-import * as middleware from './middleware/actionTypes'
import * as modals from './modals/actionTypes'
import * as modules from './modules/actionTypes'
import * as preferences from './preferences/actionTypes'
@@ -18,13 +15,10 @@ export {
analytics,
cache,
core,
- components,
form,
alerts,
auth,
- goals,
logs,
- middleware,
modals,
modules,
preferences,
diff --git a/packages/security-process/src/data/actions.js b/packages/security-process/src/data/actions.js
index df3f3836e4c..8f716519a00 100644
--- a/packages/security-process/src/data/actions.js
+++ b/packages/security-process/src/data/actions.js
@@ -6,7 +6,6 @@ import * as cache from './cache/actions'
import * as components from './components/actions'
import * as goals from './goals/actions'
import * as logs from './logs/actions'
-import * as middleware from './middleware/actions'
import * as modals from './modals/actions'
import * as modules from './modules/actions'
import * as form from './form/actions'
@@ -26,7 +25,6 @@ export {
goals,
logs,
form,
- middleware,
modals,
modules,
preferences,
diff --git a/packages/security-process/src/data/auth/actionTypes.js b/packages/security-process/src/data/auth/actionTypes.js
index 0de985229bb..14e453788a5 100644
--- a/packages/security-process/src/data/auth/actionTypes.js
+++ b/packages/security-process/src/data/auth/actionTypes.js
@@ -3,6 +3,7 @@ export const DEAUTHORIZE_BROWSER = 'DEAUTHORIZE_BROWSER'
export const LOGIN = 'LOGIN'
export const LOGIN_FAILURE = 'LOGIN_FAILURE'
export const LOGIN_LOADING = 'LOGIN_LOADING'
+export const LOGIN_ROUTINE = 'LOGIN_ROUTINE'
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS'
export const LOGOUT = 'LOGOUT'
export const LOGOUT_CLEAR_REDUX_STORE = 'LOGOUT_CLEAR_REDUX_STORE'
diff --git a/packages/security-process/src/data/auth/actions.js b/packages/security-process/src/data/auth/actions.js
index 63600f6a11a..f1845c62e56 100644
--- a/packages/security-process/src/data/auth/actions.js
+++ b/packages/security-process/src/data/auth/actions.js
@@ -7,6 +7,12 @@ export const login = (guid, password, code, sharedKey, mobileLogin) => ({
payload: { guid, password, code, sharedKey, mobileLogin }
})
export const loginLoading = () => ({ type: AT.LOGIN_LOADING })
+
+export const loginRoutine = (mobileLogin = false, firstLogin = false) => ({
+ type: AT.LOGIN_ROUTINE,
+ payload: { firstLogin, mobileLogin }
+})
+
export const loginSuccess = () => ({ type: AT.LOGIN_SUCCESS, payload: {} })
export const loginFailure = err => ({
type: AT.LOGIN_FAILURE,
diff --git a/packages/security-process/src/data/auth/sagaRegister.js b/packages/security-process/src/data/auth/sagaRegister.js
index 7bff80754c1..6d1e80ba8dc 100644
--- a/packages/security-process/src/data/auth/sagaRegister.js
+++ b/packages/security-process/src/data/auth/sagaRegister.js
@@ -2,12 +2,13 @@ import { takeLatest } from 'redux-saga/effects'
import * as AT from './actionTypes'
import sagas from './sagas'
-export default ({ api, coreSagas }) => {
- const authSagas = sagas({ api, coreSagas })
+export default (...args) => {
+ const authSagas = sagas(...args)
return function * authSaga () {
yield takeLatest(AT.DEAUTHORIZE_BROWSER, authSagas.deauthorizeBrowser)
yield takeLatest(AT.LOGIN, authSagas.login)
+ yield takeLatest(AT.LOGIN_ROUTINE, authSagas.loginRoutineSaga)
yield takeLatest(AT.LOGOUT, authSagas.logout)
yield takeLatest(
AT.LOGOUT_CLEAR_REDUX_STORE,
diff --git a/packages/security-process/src/data/auth/sagas.js b/packages/security-process/src/data/auth/sagas.js
index cde0d1b4948..8f8c2bc9f1a 100644
--- a/packages/security-process/src/data/auth/sagas.js
+++ b/packages/security-process/src/data/auth/sagas.js
@@ -5,7 +5,6 @@ import * as C from 'services/AlertService'
import * as CC from 'services/ConfirmService'
import { actions, actionTypes, model, selectors } from 'data'
import {
- askSecondPasswordEnhancer,
confirm,
promptForSecondPassword,
forceSyncWallet
@@ -28,7 +27,7 @@ export const wrongAuthCodeErrorMessage = 'Authentication code is incorrect'
const { LOGIN_EVENTS } = model.analytics
-export default ({ api, coreSagas }) => {
+export default ({ api, coreSagas, imports }) => {
const upgradeWallet = function * () {
try {
let password = yield call(promptForSecondPassword)
@@ -109,7 +108,8 @@ export default ({ api, coreSagas }) => {
)).getOrElse(false)
if (userFlowSupported) yield put(actions.modules.profile.signIn())
}
- const loginRoutineSaga = function * (mobileLogin, firstLogin) {
+
+ const loginRoutineSaga = function * ({ payload: { mobileLogin, firstLogin } }) {
try {
// If needed, the user should upgrade its wallet before being able to open the wallet
const isHdWallet = yield select(selectors.core.wallet.isHdWallet)
@@ -117,42 +117,17 @@ export default ({ api, coreSagas }) => {
yield call(upgradeWalletSaga)
}
yield put(actions.auth.authenticate())
- yield call(coreSagas.kvStore.root.fetchRoot, askSecondPasswordEnhancer)
- // If there was no eth metadata kv store entry, we need to create one and that requires the second password.
- yield call(
- coreSagas.kvStore.eth.fetchMetadataEth,
- askSecondPasswordEnhancer
- )
- yield call(
- coreSagas.kvStore.xlm.fetchMetadataXlm,
- askSecondPasswordEnhancer
+
+ imports.mainProcessDispatch(
+ actions.auth.loginRoutine(mobileLogin, firstLogin)
)
- yield call(coreSagas.kvStore.bch.fetchMetadataBch)
- yield call(coreSagas.kvStore.lockbox.fetchMetadataLockbox)
- yield put(actions.router.push('/home'))
- yield call(coreSagas.settings.fetchSettings)
- yield call(coreSagas.data.xlm.fetchLedgerDetails)
- yield call(coreSagas.data.xlm.fetchData)
- yield call(authNabu)
- yield call(upgradeAddressLabelsSaga)
- yield put(actions.auth.loginSuccess())
- yield put(actions.auth.startLogoutTimer())
- yield call(startSockets)
+
const guid = yield select(selectors.core.wallet.getGuid)
// store guid in cache for future login
yield put(actions.cache.guidEntered(guid))
// reset auth type and clear previous login form state
yield put(actions.auth.setAuthType(0))
yield put(actions.form.destroy('login'))
- // set payload language to settings language
- const language = yield select(selectors.preferences.getLanguage)
- yield put(actions.modules.settings.updateLanguage(language))
- yield put(actions.analytics.initUserSession())
- yield fork(transferEthSaga)
- yield call(saveGoals, firstLogin)
- yield put(actions.goals.runGoals())
- yield fork(checkDataErrors)
- yield fork(logoutRoutine, yield call(setLogoutEventListener))
} catch (e) {
yield put(
actions.logs.logErrorMessage(logLocation, 'loginRoutineSaga', e)
@@ -224,7 +199,7 @@ export default ({ api, coreSagas }) => {
password,
code
})
- yield call(loginRoutineSaga, mobileLogin)
+ yield put(actions.auth.loginRoutine(mobileLogin))
} catch (error) {
const initialError = prop('initial_error', error)
const authRequired = prop('authorization_required', error)
@@ -247,7 +222,7 @@ export default ({ api, coreSagas }) => {
session,
password
})
- yield call(loginRoutineSaga, mobileLogin)
+ yield put(actions.auth.loginRoutine(mobileLogin))
} catch (error) {
if (error && error.auth_type > 0) {
yield put(actions.auth.setAuthType(error.auth_type))
@@ -333,7 +308,7 @@ export default ({ api, coreSagas }) => {
yield put(actions.auth.registerLoading())
yield call(coreSagas.wallet.createWalletSaga, action.payload)
yield put(actions.alerts.displaySuccess(C.REGISTER_SUCCESS))
- yield call(loginRoutineSaga, false, true)
+ yield put(actions.auth.loginRoutine(false, true))
yield put(actions.auth.registerSuccess())
} catch (e) {
yield put(actions.auth.registerFailure())
@@ -347,7 +322,7 @@ export default ({ api, coreSagas }) => {
yield put(actions.alerts.displayInfo(C.RESTORE_WALLET_INFO))
yield call(coreSagas.wallet.restoreWalletSaga, action.payload)
yield put(actions.alerts.displaySuccess(C.RESTORE_SUCCESS))
- yield call(loginRoutineSaga, false, true)
+ yield put(actions.auth.loginRoutine(false, true))
yield put(actions.auth.restoreSuccess())
} catch (e) {
yield put(actions.auth.restoreFailure())
@@ -456,12 +431,7 @@ export default ({ api, coreSagas }) => {
)).getOrElse(false)
if (userFlowSupported) {
yield put(actions.modules.profile.clearSession())
- yield put(actions.middleware.webSocket.rates.stopSocket())
}
- yield put(actions.middleware.webSocket.bch.stopSocket())
- yield put(actions.middleware.webSocket.btc.stopSocket())
- yield put(actions.middleware.webSocket.eth.stopSocket())
- yield put(actions.middleware.webSocket.xlm.stopStreams())
// only show browser de-auth page to accounts with verified email
isEmailVerified.getOrElse(0)
? yield put(actions.router.push('/logout'))
@@ -485,8 +455,7 @@ export default ({ api, coreSagas }) => {
}
const logoutClearReduxStore = function * () {
// router will fallback to /login route
- yield window.history.pushState('', '', '#')
- yield window.location.reload(true)
+ yield imports.logout()
}
return {
diff --git a/packages/security-process/src/data/auth/sagas.spec.js b/packages/security-process/src/data/auth/sagas.spec.js
index a5dedcd54f8..96ee9c782a3 100644
--- a/packages/security-process/src/data/auth/sagas.spec.js
+++ b/packages/security-process/src/data/auth/sagas.spec.js
@@ -1,8 +1,8 @@
import { select } from 'redux-saga/effects'
import { expectSaga, testSaga } from 'redux-saga-test-plan'
-import { fork, call } from 'redux-saga-test-plan/matchers'
+import { call } from 'redux-saga-test-plan/matchers'
-import { askSecondPasswordEnhancer, confirm } from 'services/SagaService'
+import { confirm } from 'services/SagaService'
import { coreSagasFactory, Remote } from 'blockchain-wallet-v4/src'
import * as selectors from '../selectors'
import * as actions from '../actions'
@@ -30,25 +30,15 @@ const VULNERABLE_ADDRESS = '1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH'
describe('authSagas', () => {
// Mocking Math.random() to have identical popup ids for action testing
const originalMath = Object.create(Math)
- let pushStateSpy
- let locationReloadSpy
beforeAll(() => {
Math.random = () => 0.5
- pushStateSpy = jest
- .spyOn(window.history, 'pushState')
- .mockImplementation(() => {})
- locationReloadSpy = jest
- .spyOn(window.location, 'reload')
- .mockImplementation(() => {})
})
afterAll(() => {
global.Math = originalMath
- pushStateSpy.restore()
- locationReloadSpy.restore()
})
describe('login flow', () => {
- const { login, loginRoutineSaga, pollingSession } = authSagas({
+ const { login, pollingSession } = authSagas({
api,
coreSagas
})
@@ -93,11 +83,11 @@ describe('authSagas', () => {
})
})
- it('should call login routine', () => {
+ it('should dispatch login routine action', () => {
const { mobileLogin } = payload
saga
.next()
- .call(loginRoutineSaga, mobileLogin)
+ .put(actions.auth.loginRoutine(mobileLogin))
.next()
.isDone()
})
@@ -185,9 +175,9 @@ describe('authSagas', () => {
})
})
- it('should call login routine', () => {
+ it('should put login routine', () => {
const { mobileLogin } = payload
- saga.next().call(loginRoutineSaga, mobileLogin)
+ saga.next().put(actions.auth.loginRoutine(mobileLogin))
})
it('should follow 2FA flow on auth error', () => {
@@ -335,104 +325,14 @@ describe('authSagas', () => {
})
describe('login routine', () => {
- const {
- authNabu,
- checkDataErrors,
- loginRoutineSaga,
- logoutRoutine,
- saveGoals,
- setLogoutEventListener,
- startSockets,
- transferEthSaga,
- upgradeWalletSaga,
- upgradeAddressLabelsSaga
- } = authSagas({
+ const { loginRoutineSaga } = authSagas({
api,
coreSagas
})
- const mobileLogin = true
- const firstLogin = false
- const saga = testSaga(loginRoutineSaga, mobileLogin, firstLogin)
- const beforeHdCheck = 'beforeHdCheck'
-
- it('should check if wallet is an hd wallet', () => {
- saga
- .next()
- .select(selectors.core.wallet.isHdWallet)
- .save(beforeHdCheck)
- })
-
- it('should call upgradeWalletSaga if wallet is not hd', () => {
- saga
- .next(false)
- .call(upgradeWalletSaga)
- .restore(beforeHdCheck)
- })
+ const saga = testSaga(loginRoutineSaga)
it('should put authenticate action', () => {
- saga.next(true).put(actions.auth.authenticate())
- })
-
- it('should fetch root', () => {
- saga
- .next()
- .call(coreSagas.kvStore.root.fetchRoot, askSecondPasswordEnhancer)
- })
-
- it('should fetch eth metadata', () => {
- saga
- .next()
- .call(coreSagas.kvStore.eth.fetchMetadataEth, askSecondPasswordEnhancer)
- })
-
- it('should fetch xlm metadata', () => {
- saga
- .next()
- .call(coreSagas.kvStore.xlm.fetchMetadataXlm, askSecondPasswordEnhancer)
- })
-
- it('should fetch bch metadata', () => {
- saga.next().call(coreSagas.kvStore.bch.fetchMetadataBch)
- })
-
- it('should fetch lockbox metadata', () => {
- saga.next().call(coreSagas.kvStore.lockbox.fetchMetadataLockbox)
- })
-
- it('should redirect to home route', () => {
- saga.next().put(actions.router.push('/home'))
- })
-
- it('should fetch settings', () => {
- saga.next().call(coreSagas.settings.fetchSettings)
- })
-
- it('should fetch xlm ledger details', () => {
- saga.next().call(coreSagas.data.xlm.fetchLedgerDetails)
- })
-
- it('should fetch xlm accounts', () => {
- saga.next().call(coreSagas.data.xlm.fetchData)
- })
-
- it('should call auth nabu saga', () => {
- saga.next().call(authNabu)
- })
-
- it('should call upgrade address labels saga', () => {
- saga.next().call(upgradeAddressLabelsSaga)
- })
-
- it('should trigger login success action', () => {
- saga.next().put(actions.auth.loginSuccess())
- })
-
- it('should start logout timer', () => {
- saga.next().put(actions.auth.startLogoutTimer())
- })
-
- it('should start sockets', () => {
- saga.next().call(startSockets)
+ saga.next().put(actions.auth.authenticate())
})
it('should select guid from state', () => {
@@ -452,60 +352,6 @@ describe('authSagas', () => {
saga.next().put(actions.form.destroy('login'))
})
- it('should select current language', () => {
- saga.next().select(selectors.preferences.getLanguage)
- })
-
- it('should trigger update language action with selected language', () => {
- const language = 'en'
- saga.next(language).put(actions.modules.settings.updateLanguage(language))
- })
-
- it('should init analytics user session', () => {
- saga.next().put(actions.analytics.initUserSession())
- })
-
- it('should launch transferEth saga', () => {
- saga.next().fork(transferEthSaga)
- })
-
- it('should save goals', () => {
- saga.next().call(saveGoals, false)
- })
-
- it('should run goals', () => {
- saga.next().put(actions.goals.runGoals())
- })
-
- it('should check for data errors', () => {
- saga.next().fork(checkDataErrors)
- })
-
- it('should start listening for logout event', () => {
- saga.next().call(setLogoutEventListener)
- })
-
- it('should launch logout routine saga upon logout event', () => {
- const stubLogoutEvent = {}
- saga.next(stubLogoutEvent).fork(logoutRoutine, stubLogoutEvent)
- })
-
- it("should not display success if it's first login", () => {
- const firstLogin = true
- return expectSaga(loginRoutineSaga, mobileLogin, firstLogin)
- .provide([
- // Every async or value returning yield has to be mocked
- // for saga to progress
- [select(selectors.core.wallet.isHdWallet), true],
- [select(selectors.core.wallet.getGuid), 12],
- [fork.fn(transferEthSaga), jest.fn],
- [call.fn(setLogoutEventListener), jest.fn],
- [fork.fn(logoutRoutine), jest.fn]
- ])
- .not.put(actions.alerts.displaySuccess(C.LOGIN_SUCCESS))
- .run()
- })
-
describe('error handling', () => {
it('should log error', () => {
const error = {}
@@ -527,7 +373,7 @@ describe('authSagas', () => {
})
describe('register flow', () => {
- const { loginRoutineSaga, register } = authSagas({
+ const { register } = authSagas({
api,
coreSagas
})
@@ -550,10 +396,10 @@ describe('authSagas', () => {
saga.next().put(actions.alerts.displaySuccess(C.REGISTER_SUCCESS))
})
- it('should call login routine saga with falsy mobileLogin and truthy firstLogin', () => {
+ it('should dispatch login routine action with falsy mobileLogin and truthy firstLogin', () => {
const mobileLogin = false
const firstLogin = true
- saga.next().call(loginRoutineSaga, mobileLogin, firstLogin)
+ saga.next().put(actions.auth.loginRoutine(mobileLogin, firstLogin))
})
it('should finally trigger action that restore is successful', () => {
@@ -586,7 +432,7 @@ describe('authSagas', () => {
})
describe('restore flow', () => {
- const { loginRoutineSaga, restore } = authSagas({
+ const { restore } = authSagas({
api,
coreSagas
})
@@ -615,10 +461,10 @@ describe('authSagas', () => {
saga.next().put(actions.alerts.displaySuccess(C.RESTORE_SUCCESS))
})
- it('should call login routine saga with falsy mobileLogin and truthy firstLogin', () => {
+ it('should dispatch a login routine saga with falsy mobileLogin and truthy firstLogin', () => {
const mobileLogin = false
const firstLogin = true
- saga.next().call(loginRoutineSaga, mobileLogin, firstLogin)
+ saga.next().put(actions.auth.loginRoutine(mobileLogin, firstLogin))
})
it('should finally trigger action that restore is successful', () => {
@@ -990,21 +836,11 @@ describe('authSagas', () => {
describe('logout routine', () => {
const { logout } = authSagas({
api,
- coreSagas
+ coreSagas,
+ imports: { logout: jest.fn() }
})
- it('should stop rates scoket if user flow is supported', () => {
- return expectSaga(logout)
- .provide([
- [select(selectors.core.settings.getEmailVerified), Remote.of(true)],
- [select(selectors.modules.profile.userFlowSupported), Remote.of(true)]
- ])
- .put(actions.modules.profile.clearSession())
- .put(actions.middleware.webSocket.rates.stopSocket())
- .run()
- })
-
- it('should not stop rates scoket if user flow is supported', () => {
+ it('should redirect to logout if email is verified', () => {
return expectSaga(logout)
.provide([
[select(selectors.core.settings.getEmailVerified), Remote.of(true)],
@@ -1013,78 +849,20 @@ describe('authSagas', () => {
Remote.of(false)
]
])
- .not.put(actions.modules.profile.clearSession())
- .not.put(actions.middleware.webSocket.rates.stopSocket())
- .run()
- })
-
- it('should stop sockets and redirect to logout if email is verified', () => {
- return expectSaga(logout)
- .provide([
- [select(selectors.core.settings.getEmailVerified), Remote.of(true)],
- [
- select(selectors.modules.profile.userFlowSupported),
- Remote.of(false)
- ]
- ])
- .put(actions.middleware.webSocket.bch.stopSocket())
- .put(actions.middleware.webSocket.btc.stopSocket())
- .put(actions.middleware.webSocket.eth.stopSocket())
- .put(actions.middleware.webSocket.xlm.stopStreams())
.put(actions.router.push('/logout'))
.run()
})
-
- it('should stop sockets and clear redux store if email is not verified', async () => {
- return expectSaga(logout)
- .provide([
- [select(selectors.core.settings.getEmailVerified), Remote.of(false)],
- [
- select(selectors.modules.profile.userFlowSupported),
- Remote.of(false)
- ]
- ])
- .put(actions.middleware.webSocket.bch.stopSocket())
- .put(actions.middleware.webSocket.btc.stopSocket())
- .put(actions.middleware.webSocket.eth.stopSocket())
- .put(actions.middleware.webSocket.xlm.stopStreams())
- .run()
- .then(() => {
- expect(pushStateSpy).toHaveBeenCalledTimes(1)
- expect(pushStateSpy).toHaveBeenCalledWith('', '', '#')
- })
- })
})
describe('deauthorization of browser', () => {
const { deauthorizeBrowser } = authSagas({
api,
- coreSagas
+ coreSagas,
+ imports: { logout: jest.fn() }
})
const saga = testSaga(deauthorizeBrowser)
const beforeCatch = 'beforeCatch'
- const pageReloadTest = () =>
- it('should push login to url to history and reload window', () => {
- pushStateSpy.mockReset()
- locationReloadSpy.mockReset()
-
- saga
- .next()
- .inspect(gen => {
- // Inside the called saga
- gen.next()
- expect(pushStateSpy).toHaveBeenCalledTimes(1)
- expect(pushStateSpy).toHaveBeenCalledWith('', '', '#')
-
- gen.next()
- expect(locationReloadSpy).toHaveBeenCalledTimes(1)
- expect(locationReloadSpy).toHaveBeenCalledWith(true)
- })
- .next()
- .isDone()
- })
-
it('should select guid', () => {
saga.next().select(selectors.core.wallet.getGuid)
})
@@ -1106,8 +884,6 @@ describe('authSagas', () => {
.save(beforeCatch)
})
- pageReloadTest()
-
describe('error handling', () => {
beforeAll(() => {
saga.restore(beforeCatch)
@@ -1131,8 +907,6 @@ describe('authSagas', () => {
.next()
.put(actions.alerts.displayError(C.DEAUTHORIZE_BROWSER_ERROR))
})
-
- pageReloadTest()
})
})
diff --git a/packages/security-process/src/data/components/actions.js b/packages/security-process/src/data/components/actions.js
index c01e1ebdf3d..c1b4d155991 100644
--- a/packages/security-process/src/data/components/actions.js
+++ b/packages/security-process/src/data/components/actions.js
@@ -1,65 +1,3 @@
-import * as activityList from './activityList/actions'
-import * as bchTransactions from './bchTransactions/actions'
-import * as btcTransactions from './btcTransactions/actions'
-import * as coinify from './coinify/actions'
-import * as ethTransactions from './ethTransactions/actions'
-import * as xlmTransactions from './xlmTransactions/actions'
-import * as exchange from './exchange/actions'
-import * as exchangeHistory from './exchangeHistory/actions'
-import * as identityVerification from './identityVerification/actions'
-import * as importBtcAddress from './importBtcAddress/actions'
-import * as layoutWallet from './layoutWallet/actions'
-import * as lockbox from './lockbox/actions'
-import * as manageAddresses from './manageAddresses/actions'
-import * as onboarding from './onboarding/actions'
-import * as onfido from './onfido/actions'
import * as priceChart from './priceChart/actions'
-import * as priceTicker from './priceTicker/actions'
-import * as refresh from './refresh/actions'
-import * as requestBtc from './requestBtc/actions'
-import * as requestBch from './requestBch/actions'
-import * as requestEth from './requestEth/actions'
-import * as requestXlm from './requestXlm/actions'
-import * as sendBch from './sendBch/actions'
-import * as sendBtc from './sendBtc/actions'
-import * as sendEth from './sendEth/actions'
-import * as sendXlm from './sendXlm/actions'
-import * as settings from './settings/actions'
-import * as signMessage from './signMessage/actions'
-import * as transactionReport from './transactionReport/actions'
-import * as uploadDocuments from './uploadDocuments/actions'
-import * as veriff from './veriff/actions'
-export {
- activityList,
- bchTransactions,
- btcTransactions,
- coinify,
- ethTransactions,
- xlmTransactions,
- exchange,
- exchangeHistory,
- identityVerification,
- importBtcAddress,
- manageAddresses,
- onboarding,
- onfido,
- layoutWallet,
- lockbox,
- priceChart,
- priceTicker,
- refresh,
- requestBtc,
- requestBch,
- requestEth,
- requestXlm,
- sendBch,
- sendBtc,
- sendEth,
- sendXlm,
- settings,
- signMessage,
- transactionReport,
- uploadDocuments,
- veriff
-}
+export { priceChart }
diff --git a/packages/security-process/src/data/components/sagaRegister.js b/packages/security-process/src/data/components/sagaRegister.js
index 3fb331706d6..994cd40585a 100644
--- a/packages/security-process/src/data/components/sagaRegister.js
+++ b/packages/security-process/src/data/components/sagaRegister.js
@@ -1,65 +1,7 @@
import { fork } from 'redux-saga/effects'
-import activityList from './activityList/sagaRegister'
-import bchTransactions from './bchTransactions/sagaRegister'
-import btcTransactions from './btcTransactions/sagaRegister'
-import coinify from './coinify/sagaRegister'
-import ethTransactions from './ethTransactions/sagaRegister'
-import xlmTransactions from './xlmTransactions/sagaRegister'
-import exchange from './exchange/sagaRegister'
-import exchangeHistory from './exchangeHistory/sagaRegister'
-import identityVerification from './identityVerification/sagaRegister'
-import importBtcAddress from './importBtcAddress/sagaRegister'
-import lockbox from './lockbox/sagaRegister'
-import manageAddresses from './manageAddresses/sagaRegister'
-import onboarding from './onboarding/sagaRegister'
-import onfido from './onfido/sagaRegister'
import priceChart from './priceChart/sagaRegister'
-import priceTicker from './priceTicker/sagaRegister'
-import refresh from './refresh/sagaRegister'
-import requestBtc from './requestBtc/sagaRegister'
-import requestBch from './requestBch/sagaRegister'
-import requestEth from './requestEth/sagaRegister'
-import requestXlm from './requestXlm/sagaRegister'
-import sendBch from './sendBch/sagaRegister'
-import sendBtc from './sendBtc/sagaRegister'
-import sendEth from './sendEth/sagaRegister'
-import sendXlm from './sendXlm/sagaRegister'
-import settings from './settings/sagaRegister'
-import signMessage from './signMessage/sagaRegister'
-import transactionReport from './transactionReport/sagaRegister'
-import uploadDocuments from './uploadDocuments/sagaRegister'
-import veriff from './veriff/sagaRegister'
export default ({ api, coreSagas, networks }) =>
function * componentsSaga () {
- yield fork(activityList())
- yield fork(bchTransactions())
- yield fork(btcTransactions())
- yield fork(coinify({ api, coreSagas, networks }))
- yield fork(ethTransactions())
- yield fork(xlmTransactions())
- yield fork(exchange({ api, coreSagas, networks }))
- yield fork(exchangeHistory({ api, coreSagas }))
- yield fork(identityVerification({ api, coreSagas }))
- yield fork(lockbox({ api, coreSagas }))
- yield fork(importBtcAddress({ api, coreSagas, networks }))
- yield fork(manageAddresses({ api, networks }))
- yield fork(onboarding())
- yield fork(onfido({ api, coreSagas }))
yield fork(priceChart({ coreSagas }))
- yield fork(priceTicker({ coreSagas }))
- yield fork(refresh())
- yield fork(requestBtc({ networks }))
- yield fork(requestBch({ networks }))
- yield fork(requestEth({ networks }))
- yield fork(requestXlm())
- yield fork(sendBch({ coreSagas, networks }))
- yield fork(sendBtc({ coreSagas, networks }))
- yield fork(sendEth({ api, coreSagas, networks }))
- yield fork(sendXlm({ api, coreSagas }))
- yield fork(settings({ coreSagas }))
- yield fork(signMessage({ coreSagas }))
- yield fork(transactionReport({ coreSagas }))
- yield fork(uploadDocuments({ api }))
- yield fork(veriff({ api, coreSagas }))
}
diff --git a/packages/security-process/src/data/goals/sagas.spec.js b/packages/security-process/src/data/goals/sagas.spec.js
index 8a196711908..7197b99c1b4 100644
--- a/packages/security-process/src/data/goals/sagas.spec.js
+++ b/packages/security-process/src/data/goals/sagas.spec.js
@@ -371,41 +371,6 @@ describe('goals sagas', () => {
})
})
- describe('runKycGoal saga', () => {
- it('should not show kyc if current tier is >= goal tier', () => {
- const saga = testSaga(runKycGoal, { id: mockGoalId, data: { tier: 2 } })
-
- saga
- .next()
- .put(actions.goals.deleteGoal(mockGoalId))
- .next()
- .call(waitForUserData)
- .next()
- .select(selectors.modules.profile.getUserTiers)
- .next(Remote.of({ current: 2 }))
- .isDone()
- })
-
- it('should show kyc if current tier is < goal tier', () => {
- const goalTier = 2
- const saga = testSaga(runKycGoal, {
- id: mockGoalId,
- data: { tier: goalTier }
- })
- saga
- .next()
- .put(actions.goals.deleteGoal(mockGoalId))
- .next()
- .call(waitForUserData)
- .next()
- .select(selectors.modules.profile.getUserTiers)
- .next(Remote.of({ current: 1 }))
- .put(actions.components.identityVerification.verifyIdentity(goalTier))
- .next()
- .isDone()
- })
- })
-
describe('runSwapGetStartedGoal saga', () => {
it('should not show modal if it has already been seen', () => {
const saga = testSaga(runSwapGetStartedGoal, { id: mockGoalId })
diff --git a/packages/security-process/src/data/model.js b/packages/security-process/src/data/model.js
index 1f1a5645183..be167d78dc8 100644
--- a/packages/security-process/src/data/model.js
+++ b/packages/security-process/src/data/model.js
@@ -1,8 +1,6 @@
import * as analytics from './analytics/model'
-import * as components from './components/model'
import * as form from './form/model'
import * as logs from './logs/model'
import * as profile from './modules/profile/model'
-import * as rates from './modules/rates/model'
-export { analytics, components, form, logs, profile, rates }
+export { analytics, form, logs, profile }
diff --git a/packages/security-process/src/data/modules/actionTypes.js b/packages/security-process/src/data/modules/actionTypes.js
index 46a21d9a2e8..c16a6c816a6 100644
--- a/packages/security-process/src/data/modules/actionTypes.js
+++ b/packages/security-process/src/data/modules/actionTypes.js
@@ -1,17 +1,4 @@
-import * as addressesBch from './addressesBch/actionTypes'
-import * as profile from './profile/actionTypes'
-import * as rates from './rates/actionTypes'
import * as settings from './settings/actionTypes'
import * as securityCenter from './securityCenter/actionTypes'
-import * as transferEth from './transferEth/actionTypes'
-import * as sfox from './sfox/actionTypes'
-export {
- addressesBch,
- profile,
- rates,
- settings,
- securityCenter,
- transferEth,
- sfox
-}
+export { settings, securityCenter }
diff --git a/packages/security-process/src/data/modules/actions.js b/packages/security-process/src/data/modules/actions.js
index 8b38e8dccf9..ef7ee4890db 100644
--- a/packages/security-process/src/data/modules/actions.js
+++ b/packages/security-process/src/data/modules/actions.js
@@ -1,17 +1,5 @@
-import * as addressesBch from './addressesBch/actions'
import * as profile from './profile/actions'
-import * as rates from './rates/actions'
import * as settings from './settings/actions'
import * as securityCenter from './securityCenter/actions'
-import * as transferEth from './transferEth/actions'
-import * as sfox from './sfox/actions'
-export {
- addressesBch,
- profile,
- rates,
- settings,
- securityCenter,
- transferEth,
- sfox
-}
+export { profile, settings, securityCenter }
diff --git a/packages/security-process/src/data/modules/sagaRegister.js b/packages/security-process/src/data/modules/sagaRegister.js
index d4d3986916f..52ef9761af5 100644
--- a/packages/security-process/src/data/modules/sagaRegister.js
+++ b/packages/security-process/src/data/modules/sagaRegister.js
@@ -1,19 +1,11 @@
import { fork } from 'redux-saga/effects'
-import addressesBch from './addressesBch/sagaRegister'
import profile from './profile/sagaRegister'
-import rates from './rates/sagaRegister'
import settings from './settings/sagaRegister'
import securityCenter from './securityCenter/sagaRegister'
-import transferEth from './transferEth/sagaRegister'
-import sfox from './sfox/sagaRegister'
-export default ({ api, coreSagas, networks }) =>
+export default (...args) =>
function * modulesSaga () {
- yield fork(addressesBch({ coreSagas, networks }))
- yield fork(profile({ api, coreSagas, networks }))
- yield fork(rates({ api }))
- yield fork(settings({ api, coreSagas }))
- yield fork(securityCenter({ coreSagas }))
- yield fork(transferEth({ coreSagas, networks }))
- yield fork(sfox({ api, coreSagas, networks }))
+ yield fork(profile(...args))
+ yield fork(settings(...args))
+ yield fork(securityCenter(...args))
}
diff --git a/packages/security-process/src/data/modules/sagas.js b/packages/security-process/src/data/modules/sagas.js
index 00b1640f76d..c1004a8d461 100644
--- a/packages/security-process/src/data/modules/sagas.js
+++ b/packages/security-process/src/data/modules/sagas.js
@@ -1,17 +1,7 @@
-import addressesBch from './addressesBch/sagas'
-import profile from './profile/sagas'
-import rates from './rates/sagas'
import settings from './settings/sagas'
import securityCenter from './securityCenter/sagas'
-import transferEth from './transferEth/sagas'
-import sfox from './sfox/sagas'
-export default ({ api, coreSagas, networks }) => ({
- addressesBch: addressesBch({ coreSagas }),
- profile: profile({ api, coreSagas, networks }),
- rates: rates({ api }),
- settings: settings({ api, coreSagas }),
- securityCenter: securityCenter({ coreSagas }),
- transferEth: transferEth({ coreSagas, networks }),
- sfox: sfox({ api, coreSagas })
+export default (...args) => ({
+ settings: settings(...args),
+ securityCenter: securityCenter(...args)
})
diff --git a/packages/security-process/src/data/modules/selectors.js b/packages/security-process/src/data/modules/selectors.js
index 195f92e0ac1..54292cb538f 100644
--- a/packages/security-process/src/data/modules/selectors.js
+++ b/packages/security-process/src/data/modules/selectors.js
@@ -1,5 +1,3 @@
import * as profile from './profile/selectors'
-import * as rates from './rates/selectors'
-import * as sfox from './sfox/selectors'
-export { profile, rates, sfox }
+export { profile }
diff --git a/packages/security-process/src/data/modules/settings/actions.js b/packages/security-process/src/data/modules/settings/actions.js
index 8618fa91037..50addc82bb9 100644
--- a/packages/security-process/src/data/modules/settings/actions.js
+++ b/packages/security-process/src/data/modules/settings/actions.js
@@ -38,11 +38,6 @@ export const verifyMobile = code => ({
payload: { code }
})
-export const updateLanguage = language => ({
- type: AT.UPDATE_LANGUAGE,
- payload: { language }
-})
-
export const updateCurrency = currency => ({
type: AT.UPDATE_CURRENCY,
payload: { currency }
diff --git a/packages/security-process/src/data/modules/settings/sagaRegister.js b/packages/security-process/src/data/modules/settings/sagaRegister.js
index 7c093289516..16ba971f84f 100644
--- a/packages/security-process/src/data/modules/settings/sagaRegister.js
+++ b/packages/security-process/src/data/modules/settings/sagaRegister.js
@@ -2,8 +2,8 @@ import { takeLatest } from 'redux-saga/effects'
import * as AT from './actionTypes'
import sagas from './sagas'
-export default ({ api, coreSagas }) => {
- const settingsSagas = sagas({ api, coreSagas })
+export default (...args) => {
+ const settingsSagas = sagas(...args)
return function * settingsModuleSaga () {
yield takeLatest(AT.INIT_SETTINGS_INFO, settingsSagas.initSettingsInfo)
@@ -19,7 +19,6 @@ export default ({ api, coreSagas }) => {
yield takeLatest(AT.UPDATE_MOBILE, settingsSagas.updateMobile)
yield takeLatest(AT.RESEND_MOBILE, settingsSagas.resendMobile)
yield takeLatest(AT.VERIFY_MOBILE, settingsSagas.verifyMobile)
- yield takeLatest(AT.UPDATE_LANGUAGE, settingsSagas.updateLanguage)
yield takeLatest(AT.UPDATE_CURRENCY, settingsSagas.updateCurrency)
yield takeLatest(AT.UPDATE_AUTO_LOGOUT, settingsSagas.updateAutoLogout)
yield takeLatest(AT.UPDATE_LOGGING_LEVEL, settingsSagas.updateLoggingLevel)
@@ -44,8 +43,5 @@ export default ({ api, coreSagas }) => {
settingsSagas.enableTwoStepYubikey
)
yield takeLatest(AT.NEW_HD_ACCOUNT, settingsSagas.newHDAccount)
- yield takeLatest(AT.SHOW_BTC_PRIV_KEY, settingsSagas.showBtcPrivateKey)
- yield takeLatest(AT.SHOW_ETH_PRIV_KEY, settingsSagas.showEthPrivateKey)
- yield takeLatest(AT.SHOW_XLM_PRIV_KEY, settingsSagas.showXlmPrivateKey)
}
}
diff --git a/packages/security-process/src/data/modules/settings/sagas.js b/packages/security-process/src/data/modules/settings/sagas.js
index 325a4145031..d1b0cf40b8d 100644
--- a/packages/security-process/src/data/modules/settings/sagas.js
+++ b/packages/security-process/src/data/modules/settings/sagas.js
@@ -3,12 +3,7 @@ import profileSagas from 'data/modules/profile/sagas'
import * as actions from '../../actions'
import * as selectors from '../../selectors'
import * as C from 'services/AlertService'
-import { addLanguageToUrl } from 'services/LocalesService'
-import {
- askSecondPasswordEnhancer,
- promptForSecondPassword
-} from 'services/SagaService'
-import { Types, utils } from 'blockchain-wallet-v4/src'
+import { askSecondPasswordEnhancer } from 'services/SagaService'
import { contains, toLower, prop, propEq, head } from 'ramda'
export const taskToPromise = t =>
@@ -137,17 +132,6 @@ export default ({ api, coreSagas }) => {
}
}
- // We prefer local storage language and update this in background for
- // things like emails and external communication with the user
- const updateLanguage = function * (action) {
- try {
- yield call(coreSagas.settings.setLanguage, action.payload)
- addLanguageToUrl(action.payload.language)
- } catch (e) {
- yield put(actions.logs.logErrorMessage(logLocation, 'updateLanguage', e))
- }
- }
-
const updateCurrency = function * (action) {
try {
yield call(coreSagas.settings.setCurrency, action.payload)
@@ -307,65 +291,6 @@ export default ({ api, coreSagas }) => {
yield put(actions.modals.closeModal())
}
- const showBtcPrivateKey = function * (action) {
- const { addr } = action.payload
- try {
- const password = yield call(promptForSecondPassword)
- const wallet = yield select(selectors.core.wallet.getWallet)
- const privT = Types.Wallet.getPrivateKeyForAddress(wallet, password, addr)
- const priv = yield call(() => taskToPromise(privT))
- yield put(actions.modules.settings.addShownBtcPrivateKey(priv))
- } catch (e) {
- yield put(
- actions.logs.logErrorMessage(logLocation, 'showBtcPrivateKey', e)
- )
- }
- }
-
- const showEthPrivateKey = function * (action) {
- const { isLegacy } = action.payload
- try {
- const password = yield call(promptForSecondPassword)
- if (isLegacy) {
- const getSeedHex = state =>
- selectors.core.wallet.getSeedHex(state, password)
- const seedHexT = yield select(getSeedHex)
- const seedHex = yield call(() => taskToPromise(seedHexT))
- const legPriv = utils.eth.getLegacyPrivateKey(seedHex).toString('hex')
- yield put(actions.modules.settings.addShownEthPrivateKey(legPriv))
- } else {
- const getMnemonic = state =>
- selectors.core.wallet.getMnemonic(state, password)
- const mnemonicT = yield select(getMnemonic)
- const mnemonic = yield call(() => taskToPromise(mnemonicT))
- let priv = utils.eth.getPrivateKey(mnemonic, 0).toString('hex')
- yield put(actions.modules.settings.addShownEthPrivateKey(priv))
- }
- } catch (e) {
- yield put(
- actions.logs.logErrorMessage(logLocation, 'showEthPrivateKey', e)
- )
- }
- }
-
- const showXlmPrivateKey = function * () {
- try {
- const password = yield call(promptForSecondPassword)
- const getMnemonic = state =>
- selectors.core.wallet.getMnemonic(state, password)
- const mnemonicT = yield select(getMnemonic)
- const mnemonic = yield call(() => taskToPromise(mnemonicT))
- const keyPair = utils.xlm.getKeyPair(mnemonic)
- yield put(
- actions.modules.settings.addShownXlmPrivateKey(keyPair.secret())
- )
- } catch (e) {
- yield put(
- actions.logs.logErrorMessage(logLocation, 'showXlmPrivateKey', e)
- )
- }
- }
-
return {
initSettingsInfo,
initSettingsPreferences,
@@ -374,7 +299,6 @@ export default ({ api, coreSagas }) => {
updateMobile,
resendMobile,
verifyMobile,
- updateLanguage,
updateCurrency,
updateAutoLogout,
updateLoggingLevel,
@@ -387,9 +311,6 @@ export default ({ api, coreSagas }) => {
enableTwoStepGoogleAuthenticator,
enableTwoStepYubikey,
newHDAccount,
- recoverySaga,
- showBtcPrivateKey,
- showEthPrivateKey,
- showXlmPrivateKey
+ recoverySaga
}
}
diff --git a/packages/security-process/src/data/modules/settings/sagas.spec.js b/packages/security-process/src/data/modules/settings/sagas.spec.js
index 5f8393ffdee..8ce1b44a2d4 100644
--- a/packages/security-process/src/data/modules/settings/sagas.spec.js
+++ b/packages/security-process/src/data/modules/settings/sagas.spec.js
@@ -1,24 +1,17 @@
-import { select } from 'redux-saga/effects'
-import { promptForSecondPassword } from 'services/SagaService'
+import { assocPath } from 'ramda'
import { testSaga, expectSaga } from 'redux-saga-test-plan'
-import * as matchers from 'redux-saga-test-plan/matchers'
import { coreSagasFactory, Remote } from 'blockchain-wallet-v4/src'
import * as actions from '../../actions'
+import { createMockWalletState, walletV3 } from 'blockchain-wallet-v4/data'
import * as selectors from '../../selectors.js'
-import settingsSagas, {
- logLocation,
- ipRestrictionError,
- taskToPromise
-} from './sagas'
+import settingsSagas, { logLocation, ipRestrictionError } from './sagas'
import * as C from 'services/AlertService'
-import { contains } from 'ramda'
jest.mock('blockchain-wallet-v4/src/redux/sagas')
const coreSagas = coreSagasFactory()
const SECRET_GOOGLE_AUTHENTICATOR_URL = 'some_url'
-const MOCK_PASSWORD = 'password'
const MOCK_GUID = '50dae286-e42e-4d67-8419-d5dcc563746c'
describe('settingsSagas', () => {
@@ -76,6 +69,79 @@ describe('settingsSagas', () => {
})
})
+ describe(`recoverySaga`, () => {
+ it(`without second password`, () => {
+ const { recoverySaga } = settingsSagas({ coreSagas })
+ const mockState = createMockWalletState(walletV3)
+
+ return expectSaga(recoverySaga, {})
+ .withState(mockState)
+ .put({
+ type: `@COMPONENT.ADD_MNEMONIC`,
+ payload: {
+ phrase: {
+ mnemonic: [
+ `fuel`,
+ `cloth`,
+ `used`,
+ `increase`,
+ `solution`,
+ `dutch`,
+ `void`,
+ `tourist`,
+ `shadow`,
+ `sound`,
+ `soldier`,
+ `chalk`
+ ]
+ }
+ }
+ })
+ .run()
+ })
+
+ it(`with second password`, () => {
+ const securityModule = {
+ decryptWithSecondPassword: () => `5de57bbf395cec88fd672fc4d9fb3a12`
+ }
+
+ const { recoverySaga } = settingsSagas({ coreSagas, securityModule })
+
+ const encryptedState = assocPath(
+ [`hd_wallets`, 0, `seed_hex`],
+ `bKcNwis6TlK2SHRvxqESO+afPtLiNgcoLmqab/F816AVUgnbu+Gc3Bdcf5MAVjBew3mrhpS2Wbrtlg/DzBFMkA==`,
+ walletV3
+ )
+
+ const mockState = createMockWalletState(encryptedState)
+
+ return expectSaga(recoverySaga, { password: `password` })
+ .withState(mockState)
+ .put({
+ type: `@COMPONENT.ADD_MNEMONIC`,
+ payload: {
+ phrase: {
+ mnemonic: [
+ `fuel`,
+ `cloth`,
+ `used`,
+ `increase`,
+ `solution`,
+ `dutch`,
+ `void`,
+ `tourist`,
+ `shadow`,
+ `sound`,
+ `soldier`,
+ `chalk`
+ ]
+ }
+ }
+ })
+ .run()
+ })
+ })
+
describe('showGoogleAuthenticatorSecretUrl', () => {
let { showGoogleAuthenticatorSecretUrl } = settingsSagas({ coreSagas })
@@ -190,36 +256,6 @@ describe('settingsSagas', () => {
})
})
- describe('updateLanguage', () => {
- let { updateLanguage } = settingsSagas({ coreSagas })
-
- let action = { payload: { language: 'ES' } }
-
- let saga = testSaga(updateLanguage, action)
-
- it('should call setLanguage', () => {
- saga.next().call(coreSagas.settings.setLanguage, action.payload)
- })
-
- it('should add the language to the url', () => {
- saga.next()
- expect(contains(action.payload.language, window.location.href)).toBe(true)
- })
-
- describe('error handling', () => {
- const error = new Error('ERROR')
- it('should log the error', () => {
- saga
- .restart()
- .next()
- .throw(error)
- .put(
- actions.logs.logErrorMessage(logLocation, 'updateLanguage', error)
- )
- })
- })
- })
-
describe('updateCurrency', () => {
let { updateCurrency } = settingsSagas({ coreSagas })
@@ -658,37 +694,4 @@ describe('settingsSagas', () => {
})
})
})
-
- describe('showBtcPrivateKey', () => {
- const { showBtcPrivateKey } = settingsSagas({ coreSagas })
-
- let action = { payload: { addr: 'address' } }
-
- let saga = testSaga(showBtcPrivateKey, action)
-
- it('should call promptForSecondPassword', () => {
- saga.next().call(promptForSecondPassword)
- })
-
- it('should select the wallet', () => {
- saga.next(MOCK_PASSWORD).select(selectors.core.wallet.getWallet)
- })
- })
-
- describe('showEthPrivateKey', () => {
- const getMnemonic = () => jest.fn()
- const { showEthPrivateKey } = settingsSagas({ coreSagas })
-
- let action = { payload: { isLegacy: false } }
-
- it('should get the mnemonic', () => {
- return expectSaga(showEthPrivateKey, action)
- .provide([
- [matchers.call.fn(promptForSecondPassword), 'password'],
- [select(getMnemonic), 'mnemonicT'],
- [matchers.call.fn(() => taskToPromise), 'mnemonic']
- ])
- .run()
- })
- })
})
diff --git a/packages/security-process/src/data/preferences/sagaRegister.js b/packages/security-process/src/data/preferences/sagaRegister.js
index 4810f281be6..cba35c94a80 100644
--- a/packages/security-process/src/data/preferences/sagaRegister.js
+++ b/packages/security-process/src/data/preferences/sagaRegister.js
@@ -2,8 +2,8 @@ import { takeLatest } from 'redux-saga/effects'
import * as AT from './actionTypes'
import sagas from './sagas'
-export default () => {
- const preferencesSagas = sagas()
+export default (...args) => {
+ const preferencesSagas = sagas(...args)
return function * preferencesSaga () {
yield takeLatest(AT.SET_LANGUAGE, preferencesSagas.setLanguage)
diff --git a/packages/security-process/src/data/preferences/sagas.js b/packages/security-process/src/data/preferences/sagas.js
index 611eb8d13c5..30d821e4122 100644
--- a/packages/security-process/src/data/preferences/sagas.js
+++ b/packages/security-process/src/data/preferences/sagas.js
@@ -1,9 +1,8 @@
import { put } from 'redux-saga/effects'
import * as actions from '../actions.js'
import * as C from 'services/AlertService'
-import { addLanguageToUrl } from 'services/LocalesService'
-export default () => {
+export default ({ imports: { addLanguageToUrl } }) => {
const logLocation = 'preferences/sagas'
const setLanguage = function * (action) {
diff --git a/packages/security-process/src/data/rootReducer.js b/packages/security-process/src/data/rootReducer.js
index 5bdefdee9f0..e85a94f7e4a 100644
--- a/packages/security-process/src/data/rootReducer.js
+++ b/packages/security-process/src/data/rootReducer.js
@@ -2,7 +2,6 @@ import { coreReducers, paths } from 'blockchain-wallet-v4/src'
import alertsReducer from './alerts/reducers'
import analyticsReducer from './analytics/reducers'
import authReducer from './auth/reducers'
-import componentsReducer from './components/reducers'
import formReducer from './form/reducers'
import cacheReducer from './cache/reducers'
import goalsReducer from './goals/reducers'
@@ -10,31 +9,24 @@ import logsReducer from './logs/reducers'
import modalsReducer from './modals/reducers'
import preferencesReducer from './preferences/reducers'
import profileReducer from './modules/profile/reducers'
-import ratesReducer from './modules/rates/reducers'
import sessionReducer from './session/reducers'
import wizardReducer from './wizard/reducers'
import settingsReducer from './modules/settings/reducers'
-import sfoxSignupReducer from './modules/sfox/reducers'
-import qaReducer from './modules/qa/reducers'
const rootReducer = {
alerts: alertsReducer,
analytics: analyticsReducer,
auth: authReducer,
- components: componentsReducer,
form: formReducer,
goals: goalsReducer,
modals: modalsReducer,
logs: logsReducer,
preferences: preferencesReducer,
profile: profileReducer,
- rates: ratesReducer,
cache: cacheReducer,
session: sessionReducer,
wizard: wizardReducer,
securityCenter: settingsReducer,
- sfoxSignup: sfoxSignupReducer,
- qa: qaReducer,
[paths.dataPath]: coreReducers.data,
[paths.walletPath]: coreReducers.wallet,
[paths.settingsPath]: coreReducers.settings,
diff --git a/packages/security-process/src/data/rootSaga.js b/packages/security-process/src/data/rootSaga.js
index 49b086cfd5a..09691b353c8 100644
--- a/packages/security-process/src/data/rootSaga.js
+++ b/packages/security-process/src/data/rootSaga.js
@@ -5,7 +5,6 @@ import alerts from './alerts/sagaRegister'
import analytics from './analytics/sagaRegister'
import auth from './auth/sagaRegister'
import components from './components/sagaRegister'
-import middleware from './middleware/sagaRegister'
import modules from './modules/sagaRegister'
import preferences from './preferences/sagaRegister'
import goals from './goals/sagaRegister'
@@ -37,10 +36,10 @@ const welcomeSaga = function * () {
}
}
-const languageInitSaga = function * () {
+const languageInitSaga = function * ({ imports }) {
try {
yield delay(250)
- const lang = tryParseLanguageFromUrl()
+ const lang = tryParseLanguageFromUrl(imports)
if (lang.language) {
yield put(actions.preferences.setLanguage(lang.language, false))
if (lang.cultureCode) {
@@ -52,30 +51,21 @@ const languageInitSaga = function * () {
}
}
-export default function * rootSaga ({
- api,
- bchSocket,
- btcSocket,
- ethSocket,
- ratesSocket,
- networks,
- options
-}) {
- const coreSagas = coreSagasFactory({ api, networks, options })
+export default function * rootSaga (args) {
+ const coreSagas = coreSagasFactory(args)
yield all([
call(welcomeSaga),
fork(alerts),
- fork(analytics({ api })),
- fork(auth({ api, coreSagas })),
- fork(components({ api, coreSagas, networks, options })),
- fork(modules({ api, coreSagas, networks })),
- fork(preferences()),
- fork(goals({ api })),
- fork(wallet({ coreSagas })),
- fork(middleware({ api, bchSocket, btcSocket, ethSocket, ratesSocket })),
- fork(coreRootSagaFactory({ api, networks, options })),
- fork(router()),
- call(languageInitSaga)
+ fork(analytics({ ...args, coreSagas })),
+ fork(auth({ ...args, coreSagas })),
+ fork(components({ ...args, coreSagas })),
+ fork(modules({ ...args, coreSagas })),
+ fork(preferences({ ...args, coreSagas })),
+ fork(goals({ ...args, coreSagas })),
+ fork(wallet({ ...args, coreSagas })),
+ fork(coreRootSagaFactory({ ...args, coreSagas })),
+ fork(router({ ...args, coreSagas })),
+ call(languageInitSaga, { ...args, coreSagas })
])
}
diff --git a/packages/security-process/src/data/sagas.js b/packages/security-process/src/data/sagas.js
index 4f37cc8ca6b..f218dae6a03 100644
--- a/packages/security-process/src/data/sagas.js
+++ b/packages/security-process/src/data/sagas.js
@@ -1,19 +1,8 @@
import * as analytics from './analytics/sagas'
import * as auth from './auth/sagas'
-import * as components from './components/sagas'
-import * as middleware from './middleware/sagas'
import * as modules from './modules/sagas'
import * as preferences from './preferences/sagas'
import * as router from './router/sagas'
import * as wallet from './wallet/sagas'
-export {
- analytics,
- auth,
- components,
- middleware,
- modules,
- preferences,
- router,
- wallet
-}
+export { analytics, auth, modules, preferences, router, wallet }
diff --git a/packages/security-process/src/data/selectors.js b/packages/security-process/src/data/selectors.js
index de0ff9d95c2..57619be389e 100644
--- a/packages/security-process/src/data/selectors.js
+++ b/packages/security-process/src/data/selectors.js
@@ -3,8 +3,6 @@ import * as alerts from './alerts/selectors'
import * as analytics from './analytics/selectors'
import * as auth from './auth/selectors'
import * as cache from './cache/selectors'
-import * as components from './components/selectors'
-import * as exchange from './exchange/selectors'
import * as form from './form/selectors'
import * as goals from './goals/selectors'
import * as logs from './logs/selectors'
@@ -20,8 +18,6 @@ export {
analytics,
auth,
cache,
- components,
- exchange,
form,
core,
goals,
diff --git a/packages/security-process/src/index.dev.js b/packages/security-process/src/index.dev.js
index 453f445412a..23bd834997f 100644
--- a/packages/security-process/src/index.dev.js
+++ b/packages/security-process/src/index.dev.js
@@ -2,27 +2,35 @@ import React from 'react'
import ReactDOM from 'react-dom'
import { AppContainer } from 'react-hot-loader'
-import './favicons'
import configureStore from 'store'
import App from 'scenes/app.js'
import Error from './index.error'
-const renderApp = (Component, store, history, persistor) => {
- const render = (Component, store, history, persistor) => {
+const renderApp = (Component, root) => {
+ const render = (
+ Component,
+ { imports, securityModule, store, history, persistor }
+ ) => {
ReactDOM.render(
-
+
,
document.getElementById('app')
)
}
- render(App, store, history, persistor)
+ render(App, root)
if (module.hot) {
module.hot.accept('./scenes/app.js', () =>
- render(require('./scenes/app.js').default, store, history, persistor)
+ render(require('./scenes/app.js').default, root)
)
}
}
@@ -35,7 +43,7 @@ const renderError = e => {
configureStore()
.then(root => {
- renderApp(App, root.store, root.history, root.persistor)
+ renderApp(App, root)
})
.catch(e => {
renderError(e)
diff --git a/packages/security-process/src/index.html b/packages/security-process/src/index.html
deleted file mode 100644
index 7858507df44..00000000000
--- a/packages/security-process/src/index.html
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Blockchain Wallet - Exchange Cryptocurrency
-
-
-
-
-
-
diff --git a/packages/security-process/src/index.prod.js b/packages/security-process/src/index.prod.js
index ba3478f7cd0..340fcfbdde7 100644
--- a/packages/security-process/src/index.prod.js
+++ b/packages/security-process/src/index.prod.js
@@ -1,14 +1,22 @@
import React from 'react'
import ReactDOM from 'react-dom'
-import './favicons'
import configureStore from 'store'
import App from 'scenes/app.js'
import Error from './index.error'
-const renderApp = (Component, store, history, persistor) => {
+const renderApp = (
+ Component,
+ { imports, securityModule, store, history, persistor }
+) => {
ReactDOM.render(
- ,
+ ,
document.getElementById('app')
)
}
@@ -19,7 +27,7 @@ const renderError = () => {
configureStore()
.then(root => {
- renderApp(App, root.store, root.history, root.persistor)
+ renderApp(App, root)
})
.catch(e => {
// eslint-disable-next-line no-console
diff --git a/packages/security-process/src/layouts/Security/Header/index.js b/packages/security-process/src/layouts/Security/Header/index.js
new file mode 100644
index 00000000000..016547d6ea6
--- /dev/null
+++ b/packages/security-process/src/layouts/Security/Header/index.js
@@ -0,0 +1,63 @@
+import React from 'react'
+import { connect } from 'react-redux'
+import { Link } from 'react-router-dom'
+import styled from 'styled-components'
+
+import { Icon, Image } from 'blockchain-info-components'
+import { Navbar, NavbarBrand } from 'components/Navbar'
+
+const White = styled.div`
+ color: white;
+
+ a:link {
+ color: white;
+ }
+
+ a:visited {
+ color: white;
+ }
+`
+
+const Dashboard = styled.div`
+ padding-right: 25px;
+`
+
+const Header = props => {
+ const onCloseClick = event => {
+ props.dispatch({
+ type: `ROOT_LOCATION_CHANGE`,
+ payload: {
+ action: `PUSH`,
+ location: { hash: ``, pathname: `/home`, search: `` }
+ }
+ })
+
+ event.preventDefault()
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default connect()(Header)
diff --git a/packages/security-process/src/layouts/Security/index.js b/packages/security-process/src/layouts/Security/index.js
new file mode 100644
index 00000000000..be832963b3a
--- /dev/null
+++ b/packages/security-process/src/layouts/Security/index.js
@@ -0,0 +1,104 @@
+import { replace } from 'ramda'
+import React from 'react'
+import { connect } from 'react-redux'
+import { Redirect, Route } from 'react-router-dom'
+import styled from 'styled-components'
+
+import Alerts from 'components/Alerts'
+import Header from './Header'
+import AnalyticsTracker from 'providers/AnalyticsTracker'
+import ErrorBoundary from 'providers/ErrorBoundaryProvider'
+import Modals from 'modals'
+import { selectors } from 'data'
+
+const Wrapper = styled.div`
+ height: auto;
+ min-height: 100%;
+ width: 100%;
+ overflow: auto;
+
+ @media (min-width: 768px) {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ align-items: center;
+ height: 100%;
+ }
+`
+const HeaderContainer = styled.div`
+ position: relative;
+ width: 100%;
+
+ @media (min-width: 768px) {
+ top: 0;
+ left: 0;
+ }
+`
+const ContentContainer = styled.div`
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ overflow-y: auto;
+ margin: 0 25px;
+
+ @media (min-width: 768px) {
+ height: 100%;
+ }
+
+ @media (min-height: 1000px) {
+ height: 100%;
+ margin-top: 200px;
+ justify-content: flex-start;
+ }
+
+ @media (min-height: 1400px) {
+ height: 100%;
+ margin-top: 500px;
+ justify-content: flex-start;
+ }
+`
+
+class SecurityLayoutContainer extends React.PureComponent {
+ render () {
+ const {
+ component: Component,
+ isAuthenticated,
+ location,
+ ...rest
+ } = this.props
+
+ return isAuthenticated ? (
+
+ (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+ />
+
+ ) : (
+
+ )
+ }
+}
+
+const mapStateToProps = state => ({
+ isAuthenticated: selectors.auth.isAuthenticated(state)
+})
+
+export default connect(mapStateToProps)(SecurityLayoutContainer)
diff --git a/packages/security-process/src/modals/Settings/index.js b/packages/security-process/src/modals/Settings/index.js
index eeb2f162e5d..177173acad6 100644
--- a/packages/security-process/src/modals/Settings/index.js
+++ b/packages/security-process/src/modals/Settings/index.js
@@ -1,4 +1,3 @@
-import AutoDisconnection from './AutoDisconnection'
import ConfirmDisable2FA from './ConfirmDisable2FA'
import SecondPassword from './SecondPassword'
import TwoStepGoogleAuthenticator from './TwoStepGoogleAuthenticator'
@@ -6,7 +5,6 @@ import TwoStepSetup from './TwoStepSetup'
import TwoStepYubico from './TwoStepYubico'
export {
- AutoDisconnection,
ConfirmDisable2FA,
SecondPassword,
TwoStepGoogleAuthenticator,
diff --git a/packages/main-process/src/modals/Wallet/UpgradeWallet/index.js b/packages/security-process/src/modals/Wallet/UpgradeWallet/index.js
similarity index 100%
rename from packages/main-process/src/modals/Wallet/UpgradeWallet/index.js
rename to packages/security-process/src/modals/Wallet/UpgradeWallet/index.js
diff --git a/packages/main-process/src/modals/Wallet/UpgradeWallet/template.js b/packages/security-process/src/modals/Wallet/UpgradeWallet/template.js
similarity index 100%
rename from packages/main-process/src/modals/Wallet/UpgradeWallet/template.js
rename to packages/security-process/src/modals/Wallet/UpgradeWallet/template.js
diff --git a/packages/security-process/src/modals/Wallet/index.js b/packages/security-process/src/modals/Wallet/index.js
index 74785f29af5..84ab9913233 100644
--- a/packages/security-process/src/modals/Wallet/index.js
+++ b/packages/security-process/src/modals/Wallet/index.js
@@ -1,5 +1,4 @@
import PairingCode from './PairingCode'
-import ShowXPub from './ShowXPub'
import UpgradeWallet from './UpgradeWallet'
-export { PairingCode, ShowXPub, UpgradeWallet }
+export { PairingCode, UpgradeWallet }
diff --git a/packages/security-process/src/modals/index.js b/packages/security-process/src/modals/index.js
index ac7e4a0b7d1..b9e5f4a0f06 100644
--- a/packages/security-process/src/modals/index.js
+++ b/packages/security-process/src/modals/index.js
@@ -1,159 +1,31 @@
import React from 'react'
-import {
- DeleteAddressLabel,
- ShowUsedAddresses,
- UpgradeAddressLabels
-} from './Addresses'
-import { RequestBch, SendBch } from './Bch'
-import {
- AddBtcWallet,
- ImportBtcAddress,
- RequestBtc,
- SendBtc,
- ShowBtcPrivateKey,
- VerifyMessage
-} from './Btc'
-import {
- CoinifyBuyViaCard,
- CoinifyDeleteBank,
- CoinifyTradeDetails
-} from './Coinify'
-import {
- PaxWelcome,
- RequestEth,
- SendEth,
- ShowEthPrivateKey,
- TransferEth
-} from './Eth'
-import {
- EthAirdrop,
- ExchangeConfirm,
- ExchangeResults,
- KycDocResubmit,
- IdentityVerification,
- ShapeshiftTradeDetails,
- SunRiverLinkError,
- SwapUpgrade,
- UserExists
-} from './Exchange'
-import { Confirm, PromptInput, Support } from './Generic'
-import {
- LockboxAppManager,
- LockboxFirmware,
- LockboxSetup,
- LockboxConnectionPrompt,
- LockboxShowXPubs
-} from './Lockbox'
+import { Confirm, PromptInput } from './Generic'
import { MobileNumberChange, MobileNumberVerify } from './Mobile'
-import {
- AirdropClaim,
- AirdropSuccess,
- CoinifyUpgrade,
- LinkFromPitAccount,
- LinkToPitAccount,
- SwapGetStarted,
- UpgradeForAirdrop,
- Welcome
-} from './Onboarding'
-import Onfido from './Onfido'
import QRCode from './QRCode'
import {
- SfoxEnterMicroDeposits,
- SfoxExchangeData,
- SfoxTradeDetails
-} from './Sfox'
-import SignMessage from './SignMessage'
-import { EditTxDescription, TransactionReport } from './Transactions'
-import {
- AutoDisconnection,
ConfirmDisable2FA,
SecondPassword,
TwoStepGoogleAuthenticator,
TwoStepSetup,
TwoStepYubico
} from './Settings'
-import { PairingCode, ShowXPub, UpgradeWallet } from './Wallet'
-import {
- RequestXlm,
- SendXlm,
- ShowXlmPrivateKey,
- SunRiverWelcome,
- XlmCreateAccountLearn,
- XlmReserveLearn
-} from './Xlm'
+import { PairingCode, UpgradeWallet } from './Wallet'
const Modals = () => (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
)
diff --git a/packages/security-process/src/scenes/Home/index.js b/packages/security-process/src/scenes/Home/index.js
index 8d200bb5e57..7f0c455f6af 100644
--- a/packages/security-process/src/scenes/Home/index.js
+++ b/packages/security-process/src/scenes/Home/index.js
@@ -1,70 +1,3 @@
import React from 'react'
-import styled from 'styled-components'
-import ReactHighcharts from 'react-highcharts'
-import PriceChart from './PriceChart'
-import Balances from './Balances'
-import Banners from './Banners'
-import ThePit from './ThePit'
-
-ReactHighcharts.Highcharts.setOptions({ lang: { thousandsSep: ',' } })
-
-const Wrapper = styled.div`
- width: 100%;
- height: 100%;
- padding: 25px;
- @media (min-width: 992px) {
- padding: 15px 30px;
- }
-`
-const ColumnWrapper = styled.section`
- display: flex;
- flex-direction: column;
- justify-content: flex-start;
- width: 100%;
- @media (min-width: 992px) {
- flex-direction: row;
- }
-`
-const Column = styled.div`
- flex-direction: column;
- justify-content: flex-start;
- align-items: center;
- height: 100%;
- width: 100%;
- display: flex;
- max-width: 600px;
- box-sizing: border-box;
- padding-bottom: 25px;
- @media (max-height: 800px), (max-width: 991px) {
- height: auto;
- display: block;
- }
-`
-const ColumnLeft = styled(Column)`
- @media (min-width: 992px) {
- padding-right: 30px;
- }
-`
-const ColumnRight = styled(Column)`
- & > :not(:first-child) {
- margin-top: 20px;
- }
-`
-
-const Home = () => (
-
-
-
-
-
-
-
-
-
-
-
-
-)
-
-export default Home
+export default () => Security Home
diff --git a/packages/security-process/src/scenes/SecurityCenter/AdvancedSecurity/PairingCode/index.js b/packages/security-process/src/scenes/SecurityCenter/AdvancedSecurity/PairingCode/index.js
new file mode 100644
index 00000000000..bb637a8a36b
--- /dev/null
+++ b/packages/security-process/src/scenes/SecurityCenter/AdvancedSecurity/PairingCode/index.js
@@ -0,0 +1,89 @@
+import React from 'react'
+import styled from 'styled-components'
+import { FormattedMessage } from 'react-intl'
+import { connect } from 'react-redux'
+import { bindActionCreators } from 'redux'
+
+import { actions } from 'data'
+import { Badge, Button, Text } from 'blockchain-info-components'
+import {
+ SettingComponent,
+ SettingContainer,
+ SettingDescription,
+ SettingHeader,
+ SettingSummary
+} from 'components/Setting'
+
+const BadgesContainer = styled.div`
+ display: block;
+ padding-top: 10px;
+ & > * {
+ display: inline;
+ margin-right: 5px;
+ }
+`
+
+class PairingCode extends React.PureComponent {
+ constructor (props) {
+ super(props)
+ this.onShowCode = this.onShowCode.bind(this)
+ }
+
+ onShowCode () {
+ this.props.actions.showModal('PairingCode')
+ }
+
+ render () {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+ }
+}
+
+const mapDispatchToProps = dispatch => ({
+ actions: bindActionCreators(actions.modals, dispatch)
+})
+
+export default connect(
+ null,
+ mapDispatchToProps
+)(PairingCode)
diff --git a/packages/security-process/src/scenes/SecurityCenter/AdvancedSecurity/WalletId/index.js b/packages/security-process/src/scenes/SecurityCenter/AdvancedSecurity/WalletId/index.js
new file mode 100644
index 00000000000..7f054bab0b5
--- /dev/null
+++ b/packages/security-process/src/scenes/SecurityCenter/AdvancedSecurity/WalletId/index.js
@@ -0,0 +1,49 @@
+import React from 'react'
+import { FormattedMessage } from 'react-intl'
+import { connect } from 'react-redux'
+
+import { selectors } from 'data'
+import { Text } from 'blockchain-info-components'
+import {
+ SettingComponent,
+ SettingContainer,
+ SettingDescription,
+ SettingHeader,
+ SettingSummary
+} from 'components/Setting'
+
+const WalletId = props => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {props.guid}
+
+
+ )
+}
+
+const mapStateToProps = state => ({
+ guid: selectors.core.wallet.getGuid(state)
+})
+
+export default connect(mapStateToProps)(WalletId)
diff --git a/packages/security-process/src/scenes/SecurityCenter/AdvancedSecurity/index.js b/packages/security-process/src/scenes/SecurityCenter/AdvancedSecurity/index.js
index c5434ae3591..5efc0352f19 100644
--- a/packages/security-process/src/scenes/SecurityCenter/AdvancedSecurity/index.js
+++ b/packages/security-process/src/scenes/SecurityCenter/AdvancedSecurity/index.js
@@ -1,11 +1,15 @@
import React from 'react'
+import { FormattedMessage } from 'react-intl'
import styled from 'styled-components'
+import { Banner, Text } from 'blockchain-info-components'
import APIAccess from './APIAccess'
import IPWhitelist from './IPWhitelist'
import LoginIpRestriction from './LoginIpRestriction'
+import PairingCode from './PairingCode'
import PasswordStretching from './PasswordStretching'
import WalletAccessTor from './WalletAccessTor'
+import WalletId from './WalletId'
import TwoStepVerificationRemember from './TwoStepVerificationRemember'
import WalletPassword from './WalletPassword'
import SecondPasswordWallet from './SecondPasswordWallet'
@@ -19,6 +23,21 @@ export default class AdvancedSecurity extends React.PureComponent {
render () {
return (
+
+
+
+
+
+
+
+
+
diff --git a/packages/security-process/src/scenes/SecurityCenter/BasicSecurity/WalletRecoveryPhrase/RecordBackupPhrase/FirstStep/index.js b/packages/security-process/src/scenes/SecurityCenter/BasicSecurity/WalletRecoveryPhrase/RecordBackupPhrase/FirstStep/index.js
index f2b1e97662c..5ae71ead7a7 100644
--- a/packages/security-process/src/scenes/SecurityCenter/BasicSecurity/WalletRecoveryPhrase/RecordBackupPhrase/FirstStep/index.js
+++ b/packages/security-process/src/scenes/SecurityCenter/BasicSecurity/WalletRecoveryPhrase/RecordBackupPhrase/FirstStep/index.js
@@ -71,6 +71,8 @@ const FirstStep = props => {
href={recoveryPdf}
download='recovery_phrase.pdf'
data-e2e='downloadRecoveryPhraseLink'
+ target='_blank'
+ rel='noopener noreferrer'
>