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..6e79f9286c2 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": {
@@ -166,7 +177,7 @@
"express": "4.16.4",
"file-loader": "3.0.1",
"generate-changelog": "1.7.1",
- "html-webpack-plugin": "3.2.0",
+ "html-webpack-plugin": "4.0.0-beta.8",
"husky": "2.3.0",
"identity-obj-proxy": "3.0.0",
"istanbul": "github:Xesenix/istanbul",
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..ef9707d03d4 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,6 @@ export default ({ api }) => {
}
const calculateSignature = function * (
- password,
transaction,
transport,
scrambleKey,
@@ -75,9 +74,7 @@ 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 xlmSigner.sign({ transaction }, securityModule)
case ADDRESS_TYPES.LOCKBOX:
return yield call(
xlmSigner.signWithLockbox,
@@ -257,21 +254,17 @@ export default ({ api }) => {
return makePayment(merge(p, { transaction }))
},
- * 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')
- }
+ * sign (transport, scrambleKey) {
+ const transaction = prop('transaction', p)
+ const signed = yield call(
+ calculateSignature,
+ securityModule,
+ 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..6211d6c7718 100755
--- a/packages/blockchain-wallet-v4/src/redux/wallet/actionTypes.js
+++ b/packages/blockchain-wallet-v4/src/redux/wallet/actionTypes.js
@@ -1,4 +1,5 @@
// setters
+export const MERGE_WRAPPER = '@CORE.MERGE_WRAPPER'
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..4e6c7b28f3c 100755
--- a/packages/blockchain-wallet-v4/src/redux/wallet/actions.js
+++ b/packages/blockchain-wallet-v4/src/redux/wallet/actions.js
@@ -1,6 +1,9 @@
import * as T from './actionTypes'
// setters
+
+export const mergeWrapper = payload => ({ type: T.MERGE_WRAPPER, 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/sagas.js b/packages/blockchain-wallet-v4/src/redux/wallet/sagas.js
index 786ea5c1459..a412f675269 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(
@@ -47,14 +47,14 @@ export default ({ api, networks }) => {
if (isEncrypted) {
const task = Wrapper.traverseWallet(
Task.of,
- Wallet.decrypt(password),
+ Wallet.decrypt(securityModule, password),
wrapper
)
yield call(runTask, task, A.wallet.setWrapper)
} else {
const task = Wrapper.traverseWallet(
Task.of,
- Wallet.encrypt(password),
+ Wallet.encrypt(securityModule, password),
wrapper
)
yield call(runTask, task, A.wallet.setWrapper)
@@ -70,7 +70,7 @@ export default ({ api, networks }) => {
Date.now(),
password,
bipPass,
- { network, api }
+ { network, api, securityModule }
)
const wrapperT = walletT.map(wallet => set(Wrapper.wallet, wallet, wrapper))
yield call(runTask, wrapperT, A.wallet.setWrapper)
@@ -80,7 +80,7 @@ export default ({ api, networks }) => {
let wrapper = yield select(S.getWrapper)
let nextWrapper = Wrapper.traverseWallet(
Task.of,
- Wallet.newHDAccount(label, password, networks.btc),
+ Wallet.newHDAccount(securityModule, label, password, networks.btc),
wrapper
)
yield call(runTask, nextWrapper, A.wallet.setWrapper)
@@ -208,9 +208,19 @@ export default ({ api, networks }) => {
const isEncrypted = yield select(S.isSecondPasswordOn)
if (isEncrypted) {
const task = Task.of(wrapper)
- .chain(Wrapper.traverseWallet(Task.of, Wallet.decrypt(password)))
+ .chain(
+ Wrapper.traverseWallet(
+ Task.of,
+ Wallet.decrypt(securityModule, password)
+ )
+ )
.map(Wrapper.setBothPbkdf2Iterations(iterations))
- .chain(Wrapper.traverseWallet(Task.of, Wallet.encrypt(password)))
+ .chain(
+ Wrapper.traverseWallet(
+ Task.of,
+ Wallet.encrypt(securityModule, password)
+ )
+ )
yield call(runTask, task, A.wallet.setWrapper)
} else {
const newWrapper = Wrapper.setBothPbkdf2Iterations(iterations, wrapper)
diff --git a/packages/blockchain-wallet-v4/src/redux/wallet/selectors.js b/packages/blockchain-wallet-v4/src/redux/wallet/selectors.js
index ecaf3c3782d..2fae2a97bde 100755
--- a/packages/blockchain-wallet-v4/src/redux/wallet/selectors.js
+++ b/packages/blockchain-wallet-v4/src/redux/wallet/selectors.js
@@ -91,18 +91,6 @@ export const getHDAccounts = compose(
Wallet.selectHDAccounts,
getWallet
)
-export const getSeedHex = curry((state, password) =>
- compose(
- Wallet.getSeedHex(password),
- getWallet
- )(state)
-)
-export const getMnemonic = curry((state, password) =>
- compose(
- Wallet.getMnemonic(password),
- getWallet
- )(state)
-)
export const getDefaultAccount = compose(
HDWallet.selectDefaultAccount,
getDefaultHDWallet
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..d689cfaee3a 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 = ({ transaction }, securityModule) => {
+ const keyPair = getKeyPair(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..13cfa225b02 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'
@@ -84,10 +83,24 @@ export const deriveAccountNodeAtIndex = (seedHex, index, network) => {
.deriveHardened(index)
}
-export const generateAccount = curry((index, label, network, seedHex) => {
- let node = deriveAccountNodeAtIndex(seedHex, index, network)
+export const generateAccount = async (
+ { deriveBIP32Key },
+ secondPassword,
+ index,
+ label,
+ network
+) => {
+ const key = await deriveBIP32Key(
+ {
+ network,
+ secondPassword
+ },
+ `m/44'/0'/${index}'`
+ )
+
+ 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/KVStoreEntry.js b/packages/blockchain-wallet-v4/src/types/KVStoreEntry.js
index a9ecbfb6dae..ef1ca2b0f95 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.
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..7744c1a76ab 100755
--- a/packages/blockchain-wallet-v4/src/types/Wallet.js
+++ b/packages/blockchain-wallet-v4/src/types/Wallet.js
@@ -5,20 +5,10 @@ import Task from 'data.task'
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 { promiseToTask } from '../utils/functional'
import * as crypto from '../walletCrypto'
import { shift, shiftIProp } from './util'
import Type from './Type'
@@ -293,29 +283,27 @@ 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
- let appendAccount = curry((w, account) => {
- let accountsLens = compose(
- hdWallets,
- HDWalletList.hdwallet,
- HDWallet.accounts
+export const newHDAccount = curry(
+ (securityModule, label, password, network, wallet) => {
+ let hdWallet = HDWalletList.selectHDWallet(selectHdWallets(wallet))
+ let index = hdWallet.accounts.size
+ let appendAccount = curry((w, account) => {
+ let accountsLens = compose(
+ hdWallets,
+ HDWalletList.hdwallet,
+ HDWallet.accounts
+ )
+ let accountWithIndex = set(HDAccount.index, index, account)
+ return over(accountsLens, accounts => accounts.push(accountWithIndex), w)
+ })
+
+ return promiseToTask(
+ HDWallet.generateAccount(securityModule, password, index, label, network)
)
- let 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))
- .chain(applyCipher(wallet, password, HDAccount.encrypt))
- .map(appendAccount(wallet))
-})
+ .chain(applyCipher(securityModule, wallet, password, HDAccount.encrypt))
+ .map(appendAccount(wallet))
+ }
+)
// setLegacyAddressLabel :: String -> String -> Wallet -> Wallet
export const setLegacyAddressLabel = curry((address, label, wallet) => {
@@ -624,17 +612,6 @@ export const getSeedHex = curry((secondPassword, wallet) => {
}
})
-// getMnemonic :: String -> Wallet -> Task Error String
-export const getMnemonic = curry((secondPassword, wallet) => {
- const eitherToTask = e => e.fold(Task.rejected, Task.of)
- const entropyToMnemonic = compose(
- eitherToTask,
- Either.try(BIP39.entropyToMnemonic)
- )
- const seedHex = getSeedHex(secondPassword, wallet)
- return seedHex.chain(entropyToMnemonic)
-})
-
export const js = (
guid,
sharedKey,
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__/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..a6d2c4ca735 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'
@@ -12,42 +10,29 @@ import BigNumber from 'bignumber.js'
export const isValidAddress = address => /^0x[a-fA-F0-9]{40}$/.test(address)
/**
- * @param {string} mnemonic
+ * @param {function} deriveBIP32Key
* @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..529cf4c4ff6 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 (
+ { deriveSLIP10ed25519Key },
+ secondPassword
+) => {
+ 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..af7fce0581d 100644
--- a/packages/main-process/babel.config.js
+++ b/packages/main-process/babel.config.js
@@ -1,39 +1,51 @@
-module.exports = {
- presets: ['@babel/preset-env', '@babel/preset-react'],
- plugins: [
+module.exports = (api, baseDirectory = `.`) => {
+ // api isn't set when called from Webpack.
+ if (api) {
+ api.cache.forever()
+ }
+
+ const babelPlugins = [
'@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' }]
- ],
- ignore: [],
- env: {
- production: {
- presets: [
- ['@babel/preset-env', { modules: false }],
- '@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' }]
- ]
- },
- development: {
- presets: [
- ['@babel/preset-env', { modules: false }],
- '@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-hot-loader/babel'
+ [
+ 'module-resolver',
+ {
+ root: [`${baseDirectory}/src`],
+ alias: { data: `${baseDirectory}/src/data` }
+ }
+ ]
+ ]
+
+ return {
+ presets: ['@babel/preset-env', '@babel/preset-react'],
+ plugins: babelPlugins.concat([
+ [
+ 'react-intl',
+ { messagesDir: `${baseDirectory}/build/extractedMessages` }
]
+ ]),
+ ignore: [],
+ env: {
+ production: {
+ presets: [
+ ['@babel/preset-env', { modules: false }],
+ '@babel/preset-react'
+ ],
+ plugins: babelPlugins.concat([
+ [
+ 'react-intl',
+ { messagesDir: `${baseDirectory}/build/extractedMessages` }
+ ]
+ ])
+ },
+ development: {
+ presets: [
+ ['@babel/preset-env', { modules: false }],
+ '@babel/preset-react'
+ ],
+ plugins: babelPlugins.concat('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..e4314e07a50 100644
--- a/packages/main-process/src/data/auth/sagas.js
+++ b/packages/main-process/src/data/auth/sagas.js
@@ -109,14 +109,13 @@ 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 +140,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 +220,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 +243,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 +329,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 +343,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..0db4fa8fa99 100644
--- a/packages/main-process/src/data/modules/sagaRegister.js
+++ b/packages/main-process/src/data/modules/sagaRegister.js
@@ -7,12 +7,12 @@ import securityCenter from './securityCenter/sagaRegister'
import transferEth from './transferEth/sagaRegister'
import sfox from './sfox/sagaRegister'
-export default ({ api, coreSagas, networks }) =>
+export default ({ api, coreSagas, imports, networks }) =>
function * modulesSaga () {
yield fork(addressesBch({ coreSagas, networks }))
yield fork(profile({ api, coreSagas, networks }))
yield fork(rates({ api }))
- yield fork(settings({ api, coreSagas }))
+ yield fork(settings({ api, coreSagas, imports }))
yield fork(securityCenter({ coreSagas }))
yield fork(transferEth({ coreSagas, networks }))
yield fork(sfox({ api, coreSagas, networks }))
diff --git a/packages/main-process/src/data/modules/sagas.js b/packages/main-process/src/data/modules/sagas.js
index 00b1640f76d..e120e6fc45d 100644
--- a/packages/main-process/src/data/modules/sagas.js
+++ b/packages/main-process/src/data/modules/sagas.js
@@ -6,11 +6,11 @@ import securityCenter from './securityCenter/sagas'
import transferEth from './transferEth/sagas'
import sfox from './sfox/sagas'
-export default ({ api, coreSagas, networks }) => ({
+export default ({ api, coreSagas, imports, networks }) => ({
addressesBch: addressesBch({ coreSagas }),
profile: profile({ api, coreSagas, networks }),
rates: rates({ api }),
- settings: settings({ api, coreSagas }),
+ settings: settings({ api, coreSagas, imports }),
securityCenter: securityCenter({ coreSagas }),
transferEth: transferEth({ coreSagas, networks }),
sfox: sfox({ api, coreSagas })
diff --git a/packages/main-process/src/data/modules/settings/sagaRegister.js b/packages/main-process/src/data/modules/settings/sagaRegister.js
index 7c093289516..f59d82b1e37 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 ({ api, coreSagas, imports }) => {
+ const settingsSagas = sagas({ api, coreSagas, imports })
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..497cad8e1ac 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))
}
@@ -325,20 +304,13 @@ export default ({ api, coreSagas }) => {
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')
+ 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 = utils.eth.getPrivateKey(securityModule, 0).toString('hex')
yield put(actions.modules.settings.addShownEthPrivateKey(priv))
}
} catch (e) {
@@ -350,12 +322,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 = utils.xlm.getKeyPair(securityModule)
yield put(
actions.modules.settings.addShownXlmPrivateKey(keyPair.secret())
)
@@ -369,7 +336,6 @@ export default ({ api, coreSagas }) => {
return {
initSettingsInfo,
initSettingsPreferences,
- showBackupRecovery,
showGoogleAuthenticatorSecretUrl,
updateMobile,
resendMobile,
@@ -387,7 +353,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..19f18d6744f 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,37 @@ 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,
+ imports,
ratesSocket,
networks,
- options
+ options,
+ securityModule
}) {
- const coreSagas = coreSagasFactory({ api, networks, options })
+ const coreSagas = coreSagasFactory({
+ api,
+ imports,
+ networks,
+ options,
+ securityModule
+ })
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(components({ api, coreSagas, imports, networks, options })),
+ fork(modules({ api, coreSagas, imports, networks })),
+ fork(preferences({ imports })),
fork(goals({ api })),
fork(wallet({ coreSagas })),
fork(middleware({ api, bchSocket, btcSocket, ethSocket, ratesSocket })),
- fork(coreRootSagaFactory({ api, networks, options })),
- fork(router()),
- call(languageInitSaga)
+ fork(coreRootSagaFactory({ api, imports, networks, options })),
+ fork(router())
])
}
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(