From 244b29850cf92187229ee2bdfb493791689baa83 Mon Sep 17 00:00:00 2001 From: Thibaut Boustany Date: Thu, 23 Aug 2018 15:05:06 +0200 Subject: [PATCH 01/45] Error for when museo is missing, and OS doesn't have awscli nor apt --- scripts/release.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/release.sh b/scripts/release.sh index 3a68b4e7..297c6e53 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -25,6 +25,11 @@ fi if [ ! -d "static/fonts/museosans" ]; then if ! command -v aws ; then + if ! command -v apt ; then + echo "Museo Sans is missing, and I can't fetch it (no aws, no apt)" >&2 + exit 1 + fi + runJob "sudo apt install awscli" "installing aws cli..." "installed aws cli" "failed to install aws cli" fi From e2ab9e33b6d03693a6946caa0456d11031ca8e55 Mon Sep 17 00:00:00 2001 From: Thibaut Boustany Date: Fri, 24 Aug 2018 10:34:55 +0200 Subject: [PATCH 02/45] Draft release ourselves before electron-builder --- scripts/create-draft-release.js | 85 +++++++++++++++++++++++++++++++++ scripts/release.sh | 9 ++++ 2 files changed, 94 insertions(+) create mode 100644 scripts/create-draft-release.js diff --git a/scripts/create-draft-release.js b/scripts/create-draft-release.js new file mode 100644 index 00000000..7edb38d7 --- /dev/null +++ b/scripts/create-draft-release.js @@ -0,0 +1,85 @@ +#!/usr/bin/env node + +/* eslint-disable no-console */ + +const util = require('util') +const exec = util.promisify(require('child_process').exec) +const octokit = require('@octokit/rest')() + +const repo = { + owner: 'LedgerHQ', + repo: 'ledger-live-desktop', +} + +async function getTag() { + const { stdout } = await exec('git tag --points-at HEAD') + const tag = stdout.replace('\n', '') + + if (!tag) { + throw new Error(`Unable to get current tag. Is your HEAD on a tagged commit?`) + } + + return tag +} + +async function checkDraft(tag) { + const { status, data } = await octokit.repos.getReleases(repo) + + if (status !== 200) { + throw new Error(`Got HTTP status ${status} when trying to fetch releases list.`) + } + + for (const release of data) { + if (release.tag_name === tag) { + if (release.draft) { + return true + } + + throw new Error(`A release tagged ${tag} exists but is not a draft.`) + } + } + + return false +} + +async function createDraft(tag) { + const params = { + ...repo, + tag_name: tag, + name: tag, + draft: true, + prerelease: true, + } + + const { status } = await octokit.repos.createRelease(params) + + if (status !== 201) { + throw new Error(`Got HTTP status ${status} when trying to create the release draft.`) + } +} + +async function main() { + try { + const token = process.env.GH_TOKEN + const tag = await getTag() + + octokit.authenticate({ + type: 'token', + token, + }) + + const existingDraft = await checkDraft(tag) + + if (!existingDraft) { + console.log(`No draft exists for ${tag}, creating...`) + createDraft(tag) + } else { + console.log(`A draft already exists for ${tag}, nothing to do.`) + } + } catch (e) { + console.error(e) + process.exit(1) + } +} + +main() diff --git a/scripts/release.sh b/scripts/release.sh index 297c6e53..a0912e45 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -57,6 +57,15 @@ fi # exit 1 # fi + +if [[ $(uname) == 'Linux' ]]; then # only run it on one target, to prevent race conditions + runJob \ + "node scripts/create-draft-release.js" \ + "creating a draft release on GitHub (if needed)..." \ + "draft release ready" \ + "failed to create a draft release" +fi + runJob "yarn compile" "compiling..." "compiled" "failed to compile" "verbose" runJob \ From 0c3ebf6c05d209089a028e86db8e6abeddf0a869 Mon Sep 17 00:00:00 2001 From: Thibaut Boustany Date: Fri, 24 Aug 2018 12:03:20 +0200 Subject: [PATCH 03/45] Fix OS and version not shown on Mac --- scripts/helpers/display-env.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scripts/helpers/display-env.sh b/scripts/helpers/display-env.sh index 5f903413..2a94c8ad 100644 --- a/scripts/helpers/display-env.sh +++ b/scripts/helpers/display-env.sh @@ -7,9 +7,15 @@ if [ "$GIT_REVISION" == "" ]; then GIT_REVISION=$(git rev-parse HEAD) fi +if [[ $(uname) == 'Darwin' ]]; then + osVersion="$(sw_vers -productName) $(sw_vers -productVersion)" +else + osVersion="$(uname -srmo)" +fi + echo printf " │ \\e[4;1m%s\\e[0;0m\\n" "Ledger Live Desktop - ${GIT_REVISION}" -printf " │ \\e[1;30m%s\\e[1;0m\\n" "$(uname -srmo)" +printf " │ \\e[1;30m%s\\e[1;0m\\n" "${osVersion}" printf " │ \\e[2;1mcommit \\e[0;33m%s\\e[0;0m\\n" "$(git rev-parse HEAD)" echo From c9d8305e203bcebc4f1430cef77979dbe96bfe9b Mon Sep 17 00:00:00 2001 From: Thibaut Boustany Date: Fri, 24 Aug 2018 13:55:08 +0200 Subject: [PATCH 04/45] Add missing devDependency --- package.json | 1 + yarn.lock | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/package.json b/package.json index eeeac8c0..8f2456a1 100644 --- a/package.json +++ b/package.json @@ -119,6 +119,7 @@ "@babel/preset-flow": "7.0.0-beta.42", "@babel/preset-react": "7.0.0-beta.42", "@babel/preset-stage-0": "7.0.0-beta.42", + "@octokit/rest": "^15.10.0", "@storybook/addon-actions": "^3.4.7", "@storybook/addon-knobs": "^3.4.7", "@storybook/addon-links": "^3.4.7", diff --git a/yarn.lock b/yarn.lock index 23aa34ce..f132f37e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1474,6 +1474,13 @@ version "0.7.1" resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-0.7.1.tgz#e44e596d03c9f16ba3b127ad333a8a072bcb5a0a" +"@gimenete/type-writer@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@gimenete/type-writer/-/type-writer-0.1.3.tgz#2d4f26118b18d71f5b34ca24fdd6d1fd455c05b6" + dependencies: + camelcase "^5.0.0" + prettier "^1.13.7" + "@ledgerhq/hw-app-btc@4.21.0": version "4.21.0" resolved "https://registry.yarnpkg.com/@ledgerhq/hw-app-btc/-/hw-app-btc-4.21.0.tgz#4f94571bb3d63cd785e31a7e1f77ce597c344516" @@ -1568,6 +1575,20 @@ version "1.1.0" resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.0.tgz#50c1e2260ac0ed9439a181de3725a0168d59c48a" +"@octokit/rest@^15.10.0": + version "15.10.0" + resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-15.10.0.tgz#9baf7430e55edf1a1024c35ae72ed2f5fc6e90e9" + dependencies: + "@gimenete/type-writer" "^0.1.3" + before-after-hook "^1.1.0" + btoa-lite "^1.0.0" + debug "^3.1.0" + http-proxy-agent "^2.1.0" + https-proxy-agent "^2.2.0" + lodash "^4.17.4" + node-fetch "^2.1.1" + url-template "^2.0.8" + "@posthtml/esm@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@posthtml/esm/-/esm-1.0.0.tgz#09bcb28a02438dcee22ad1970ca1d85a000ae0cf" @@ -3584,6 +3605,10 @@ bech32@^1.1.2: version "1.1.3" resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.3.tgz#bd47a8986bbb3eec34a56a097a84b8d3e9a2dfcd" +before-after-hook@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-1.1.0.tgz#83165e15a59460d13702cb8febd6a1807896db5a" + bfj-node4@^5.2.0: version "5.3.1" resolved "https://registry.yarnpkg.com/bfj-node4/-/bfj-node4-5.3.1.tgz#e23d8b27057f1d0214fc561142ad9db998f26830" @@ -3902,6 +3927,10 @@ bser@^2.0.0: dependencies: node-int64 "^0.4.0" +btoa-lite@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/btoa-lite/-/btoa-lite-1.0.0.tgz#337766da15801210fdd956c22e9c6891ab9d0337" + buffer-alloc-unsafe@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" @@ -4162,6 +4191,10 @@ camelcase@^4.0.0, camelcase@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" +camelcase@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.0.0.tgz#03295527d58bd3cd4aa75363f35b2e8d97be2f42" + can-promise@^0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/can-promise/-/can-promise-0.0.1.tgz#7a7597ad801fb14c8b22341dfec314b6bd6ad8d3" @@ -10132,6 +10165,10 @@ node-fetch@^1.0.1: encoding "^0.1.11" is-stream "^1.0.1" +node-fetch@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.2.0.tgz#4ee79bde909262f9775f731e3656d0db55ced5b5" + node-forge@0.7.5: version "0.7.5" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.5.tgz#6c152c345ce11c52f465c2abd957e8639cd674df" @@ -11521,6 +11558,10 @@ prettier@^1.12.1, prettier@^1.13.5: version "1.13.7" resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.13.7.tgz#850f3b8af784a49a6ea2d2eaa7ed1428a34b7281" +prettier@^1.13.7: + version "1.14.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.14.2.tgz#0ac1c6e1a90baa22a62925f41963c841983282f9" + pretty-bytes@^1.0.2: version "1.0.4" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-1.0.4.tgz#0a22e8210609ad35542f8c8d5d2159aff0751c84" @@ -14515,6 +14556,10 @@ url-parse@^1.1.8, url-parse@~1.4.0: querystringify "^2.0.0" requires-port "^1.0.0" +url-template@^2.0.8: + version "2.0.8" + resolved "https://registry.yarnpkg.com/url-template/-/url-template-2.0.8.tgz#fc565a3cccbff7730c775f5641f9555791439f21" + url-to-options@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9" From 132e8ee686819fae65484f24982b24785f87dc21 Mon Sep 17 00:00:00 2001 From: Juan Cortes Ross Date: Thu, 23 Aug 2018 21:09:15 +0200 Subject: [PATCH 05/45] Fix Issue 1448 Renaming checkCanBeSpent to checkValidTransaction, dropping isValidTransaction Added the isZero check from the removed method, removed the dead code from bridges --- src/bridge/EthereumJSBridge.js | 6 ++---- src/bridge/LibcoreBridge.js | 10 ++++------ src/bridge/RippleJSBridge.js | 6 ++---- src/bridge/UnsupportedBridge.js | 4 +--- src/bridge/makeMockBridge.js | 8 +++----- src/bridge/types.js | 7 ++++--- src/components/RequestAmount/index.js | 8 ++++---- src/components/modals/Send/fields/AmountField.js | 16 ++++++++-------- .../modals/Send/steps/01-step-amount.js | 10 ++++++---- 9 files changed, 34 insertions(+), 41 deletions(-) diff --git a/src/bridge/EthereumJSBridge.js b/src/bridge/EthereumJSBridge.js index e68f70d2..2a48e6cf 100644 --- a/src/bridge/EthereumJSBridge.js +++ b/src/bridge/EthereumJSBridge.js @@ -420,15 +420,13 @@ const EthereumBridge: WalletBridge = { getTransactionRecipient: (a, t) => t.recipient, - isValidTransaction: (a, t) => (!t.amount.isZero() && t.recipient && true) || false, - EditFees, EditAdvancedOptions, - checkCanBeSpent: (a, t) => + checkValidTransaction: (a, t) => t.amount.isLessThanOrEqualTo(a.balance) - ? Promise.resolve() + ? Promise.resolve(true) : Promise.reject(new NotEnoughBalance()), getTotalSpent: (a, t) => diff --git a/src/bridge/LibcoreBridge.js b/src/bridge/LibcoreBridge.js index 642788d1..4f5d51eb 100644 --- a/src/bridge/LibcoreBridge.js +++ b/src/bridge/LibcoreBridge.js @@ -95,11 +95,11 @@ const getFees = async (a, transaction) => { return promise } -const checkCanBeSpent = (a, t) => +const checkValidTransaction = (a, t) => !t.amount - ? Promise.resolve() + ? Promise.resolve(true) : getFees(a, t) - .then(() => {}) + .then(() => true) .catch(e => { if (e.code === NOT_ENOUGH_FUNDS) { throw new NotEnoughBalance() @@ -192,9 +192,7 @@ const LibcoreBridge: WalletBridge = { // EditAdvancedOptions, - isValidTransaction: (a, t) => (!t.amount.isZero() && t.recipient && true) || false, - - checkCanBeSpent, + checkValidTransaction, getTotalSpent: (a, t) => t.amount.isZero() diff --git a/src/bridge/RippleJSBridge.js b/src/bridge/RippleJSBridge.js index f872eeb6..e3531790 100644 --- a/src/bridge/RippleJSBridge.js +++ b/src/bridge/RippleJSBridge.js @@ -474,9 +474,7 @@ const RippleJSBridge: WalletBridge = { getTransactionRecipient: (a, t) => t.recipient, - isValidTransaction: (a, t) => (!t.amount.isZero() && t.recipient && true) || false, - - checkCanBeSpent: async (a, t) => { + checkValidTransaction: async (a, t) => { const r = await getServerInfo(a.endpointConfig) if ( t.amount @@ -484,7 +482,7 @@ const RippleJSBridge: WalletBridge = { .plus(parseAPIValue(r.validatedLedger.reserveBaseXRP)) .isLessThanOrEqualTo(a.balance) ) { - return + return true } throw new NotEnoughBalance() }, diff --git a/src/bridge/UnsupportedBridge.js b/src/bridge/UnsupportedBridge.js index 6f5d5c12..ebc768e5 100644 --- a/src/bridge/UnsupportedBridge.js +++ b/src/bridge/UnsupportedBridge.js @@ -27,13 +27,11 @@ const UnsupportedBridge: WalletBridge<*> = { getTransactionAmount: () => BigNumber(0), - isValidTransaction: () => false, - editTransactionRecipient: () => null, getTransactionRecipient: () => '', - checkCanBeSpent: () => Promise.resolve(), + checkValidTransaction: () => Promise.resolve(false), getTotalSpent: () => Promise.resolve(BigNumber(0)), diff --git a/src/bridge/makeMockBridge.js b/src/bridge/makeMockBridge.js index 909dfe91..d266c234 100644 --- a/src/bridge/makeMockBridge.js +++ b/src/bridge/makeMockBridge.js @@ -18,7 +18,7 @@ const defaultOpts = { scanAccountDeviceSuccessRate: 0.8, transactionsSizeTarget: 100, extraInitialTransactionProps: () => null, - checkCanBeSpent: () => Promise.resolve(), + checkValidTransaction: () => Promise.resolve(), getTotalSpent: (a, t) => Promise.resolve(t.amount), getMaxAmount: a => Promise.resolve(a.balance), } @@ -36,7 +36,7 @@ function makeMockBridge(opts?: Opts): WalletBridge<*> { extraInitialTransactionProps, getTotalSpent, getMaxAmount, - checkCanBeSpent, + checkValidTransaction, } = { ...defaultOpts, ...opts, @@ -155,9 +155,7 @@ function makeMockBridge(opts?: Opts): WalletBridge<*> { EditAdvancedOptions, - isValidTransaction: (a, t) => (t.amount > 0 && t.recipient && true) || false, - - checkCanBeSpent, + checkValidTransaction, getTotalSpent, diff --git a/src/bridge/types.js b/src/bridge/types.js index 53a28250..eea8cefa 100644 --- a/src/bridge/types.js +++ b/src/bridge/types.js @@ -76,15 +76,16 @@ export interface WalletBridge { getTransactionRecipient(account: Account, transaction: Transaction): string; - isValidTransaction(account: Account, transaction: Transaction): boolean; - // render the whole Fees section of the form EditFees?: *; // React$ComponentType>; // render the whole advanced part of the form EditAdvancedOptions?: *; // React$ComponentType>; - checkCanBeSpent(account: Account, transaction: Transaction): Promise; + // validate the transaction and all currency specific validations here, we can return false + // to disable the button without throwing an error if we are handling the error on a different + // input or throw an error that will highlight the issue on the amount field + checkValidTransaction(account: Account, transaction: Transaction): Promise; getTotalSpent(account: Account, transaction: Transaction): Promise; diff --git a/src/components/RequestAmount/index.js b/src/components/RequestAmount/index.js index 63528897..2e9c1778 100644 --- a/src/components/RequestAmount/index.js +++ b/src/components/RequestAmount/index.js @@ -48,7 +48,7 @@ type OwnProps = { // left value (always the one which is returned) value: BigNumber, - canBeSpentError: ?Error, + validTransactionError: ?Error, // max left value max: BigNumber, @@ -113,7 +113,7 @@ const mapStateToProps = (state: State, props: OwnProps) => { export class RequestAmount extends PureComponent { static defaultProps = { max: BigNumber(Infinity), - canBeSpent: true, + validTransaction: true, withMax: true, } @@ -139,14 +139,14 @@ export class RequestAmount extends PureComponent { renderInputs(containerProps: Object) { // TODO move this inlined into render() for less spaghetti - const { value, account, rightCurrency, getCounterValue, canBeSpentError } = this.props + const { value, account, rightCurrency, getCounterValue, validTransactionError } = this.props const right = getCounterValue(value) || BigNumber(0) const rightUnit = rightCurrency.units[0] // FIXME: no way InputCurrency pure can work here. inlined InputRight (should be static func?), inline containerProps object.. return ( { +class AmountField extends Component<*, { validTransactionError: ?Error }> { state = { - canBeSpentError: null, + validTransactionError: null, } componentDidMount() { this.resync() @@ -27,11 +27,11 @@ class AmountField extends Component<*, { canBeSpentError: ?Error }> { const { account, bridge, transaction } = this.props const syncId = ++this.syncId try { - await bridge.checkCanBeSpent(account, transaction) + await bridge.checkValidTransaction(account, transaction) if (this.syncId !== syncId) return - this.setState({ canBeSpentError: null }) - } catch (canBeSpentError) { - this.setState({ canBeSpentError }) + this.setState({ validTransactionError: null }) + } catch (validTransactionError) { + this.setState({ validTransactionError }) } } @@ -42,14 +42,14 @@ class AmountField extends Component<*, { canBeSpentError: ?Error }> { render() { const { bridge, account, transaction, t } = this.props - const { canBeSpentError } = this.state + const { validTransactionError } = this.state return ( diff --git a/src/components/modals/Send/steps/01-step-amount.js b/src/components/modals/Send/steps/01-step-amount.js index 959cd92e..a38f3db7 100644 --- a/src/components/modals/Send/steps/01-step-amount.js +++ b/src/components/modals/Send/steps/01-step-amount.js @@ -134,11 +134,13 @@ export class StepAmountFooter extends PureComponent< bridge.getTransactionRecipient(account, transaction), ) if (syncId !== this.syncId) return - const canBeSpent = await bridge - .checkCanBeSpent(account, transaction) - .then(() => true, () => false) + const isValidTransaction = await bridge + .checkValidTransaction(account, transaction) + .then(result => result, () => false) + if (syncId !== this.syncId) return - const canNext = isRecipientValid && canBeSpent && totalSpent.gt(0) + const canNext = + !transaction.amount.isZero() && isRecipientValid && isValidTransaction && totalSpent.gt(0) this.setState({ totalSpent, canNext, isSyncing: false }) } catch (err) { logger.critical(err) From b1e62314688b4f2d20a22b3fab8b09b16a89a768 Mon Sep 17 00:00:00 2001 From: Juan Cortes Ross Date: Sat, 25 Aug 2018 15:55:30 +0200 Subject: [PATCH 06/45] Preparsed value for RecipientAddress to remove spaces --- src/components/RecipientAddress/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/RecipientAddress/index.js b/src/components/RecipientAddress/index.js index 24f7895d..b579ebef 100644 --- a/src/components/RecipientAddress/index.js +++ b/src/components/RecipientAddress/index.js @@ -99,9 +99,10 @@ class RecipientAddress extends PureComponent { ) : null + const preOnChange = text => onChange((text && text.replace(/\s/g, '')) || '') return ( - + ) } From d801e64ba8692777df78e1ec9427d11cda6dec8f Mon Sep 17 00:00:00 2001 From: Juan Cortes Ross Date: Sun, 26 Aug 2018 22:20:35 +0200 Subject: [PATCH 07/45] Refuse to pass the change event if there's no value selected to avoid crash from issue-1457 --- src/components/base/InputCurrency/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/base/InputCurrency/index.js b/src/components/base/InputCurrency/index.js index db6c8c8e..5fa7c9d8 100644 --- a/src/components/base/InputCurrency/index.js +++ b/src/components/base/InputCurrency/index.js @@ -172,7 +172,7 @@ class InputCurrency extends PureComponent { renderListUnits = () => { const { units, onChangeUnit, unit } = this.props const { isFocused } = this.state - + const avoidEmptyValue = value => value && onChangeUnit(value) if (units.length <= 1) { return null } @@ -180,7 +180,7 @@ class InputCurrency extends PureComponent { return ( + {onBack && ( diff --git a/src/components/modals/Disclaimer.js b/src/components/modals/Disclaimer.js index fb493733..f00b3ebd 100644 --- a/src/components/modals/Disclaimer.js +++ b/src/components/modals/Disclaimer.js @@ -25,7 +25,7 @@ class DisclaimerModal extends PureComponent { name={MODAL_DISCLAIMER} render={({ onClose }) => ( - {t('app:disclaimerModal.title')} + {t('app:disclaimerModal.title')} diff --git a/src/components/modals/ShareAnalytics.js b/src/components/modals/ShareAnalytics.js index 4365ac97..e13f47d0 100644 --- a/src/components/modals/ShareAnalytics.js +++ b/src/components/modals/ShareAnalytics.js @@ -64,13 +64,15 @@ class ShareAnalytics extends PureComponent { name={MODAL_SHARE_ANALYTICS} render={({ onClose }) => ( - {t('onboarding:analytics.shareAnalytics.title')} + + {t('onboarding:analytics.shareAnalytics.title')} + {t('onboarding:analytics.shareAnalytics.desc')}
    {items.map(item =>
  • {item.desc}
  • )}
- diff --git a/src/components/modals/TechnicalData.js b/src/components/modals/TechnicalData.js index 7a8eb164..2d55b202 100644 --- a/src/components/modals/TechnicalData.js +++ b/src/components/modals/TechnicalData.js @@ -45,7 +45,7 @@ class TechnicalData extends PureComponent { name={MODAL_TECHNICAL_DATA} render={({ onClose }) => ( - + {t('onboarding:analytics.technicalData.mandatoryContextual.title')} {t('onboarding:analytics.technicalData.desc')} @@ -53,7 +53,7 @@ class TechnicalData extends PureComponent {
    {items.map(item =>
  • {item.desc}
  • )}
- diff --git a/test-e2e/helpers/test_helpers.js b/test-e2e/helpers/test_helpers.js new file mode 100644 index 00000000..defcab86 --- /dev/null +++ b/test-e2e/helpers/test_helpers.js @@ -0,0 +1,40 @@ +import { delay } from 'helpers/promise' + +// Wait for an element to be present then continue +export function waitForExpectedText(app, selector, expected, maxRetry = 5) { + async function check() { + if (!maxRetry) { + throw new Error(`Cant find the element ${selector} in the page`) + } + try { + const str = await app.client.getText(selector) + if (str === expected) { + return true + } + } catch (err) {} // eslint-disable-line + await delay(500) + --maxRetry + return check() + } + return check() +} + +// Wait for an element to disappear then continue +export function waitForDisappear(app, selector, maxRetry = 5) { + async function check() { + if (!maxRetry) { + throw new Error('Too many retries for waiting element to disappear') + } + try { + await app.client.getText(selector) + } catch (err) { + if (err.message.startsWith('An element could not be located')) { + return true + } + } + await delay(500) + --maxRetry + return check() + } + return check() +} diff --git a/test-e2e/nav_to_settings.spec.js b/test-e2e/nav_to_settings.spec.js deleted file mode 100644 index d2516b91..00000000 --- a/test-e2e/nav_to_settings.spec.js +++ /dev/null @@ -1,64 +0,0 @@ -const Application = require('spectron').Application - -let app - -const TIMEOUT = 50 * 1000 - -describe('Application launch', () => { - beforeEach(async () => { - app = new Application({ - path: './dist/ledger-live-desktop-1.1.0-linux-x86_64.AppImage', - env: { - SKIP_ONBOARDING: '1', - }, - }) - await app.start() - }, TIMEOUT) - - afterEach(async () => { - if (app && app.isRunning()) { - await app.stop() - } - }, TIMEOUT) - - test( - 'Start app and set developper mode ', - async () => { - const title = await app.client.getTitle() - expect(title).toEqual('Ledger Live') - await app.client.waitUntilWindowLoaded() - await app.client.pause(2000) - - // Post Onboarding - const title_onboarding = await app.client.getText('[data-e2e=onboarding_title]') - expect(title_onboarding).toEqual('Analytics and bug reports') - await app.client.click('[data-e2e=continue_button]') - await app.client.pause(1000) - - const title_finish = await app.client.getText('[data-e2e=finish_title]') - expect(title_finish).toEqual('Your device is ready!') - await app.client.click('[data-e2e=continue_button]') - await app.client.pause(1000) - - const title_disclaimer = await app.client.getText('[data-e2e=disclaimer_title]') - expect(title_disclaimer).toEqual('Trade safely') - await app.client.click('[data-e2e=continue_button]') - await app.client.pause(1000) - - // Dashboard EmptyState - const title_dashboard_empty = await app.client.getText('[data-e2e=dashboard_empty_title]') - expect(title_dashboard_empty).toEqual('Add accounts to your portfolio') - - // Open Settings - await app.client.click('[data-e2e=setting_button]') - await app.client.pause(1000) - const title_settings = await app.client.getText('[data-e2e=settings_title]') - expect(title_settings).toEqual('Settings') - - // DevMode ON - await app.client.click('[data-e2e=devMode_button]') - await app.client.pause(500) - }, - TIMEOUT, - ) -}) diff --git a/test-e2e/skipOnboarding_GeneralSettingsCheck.spec.js b/test-e2e/skipOnboarding_GeneralSettingsCheck.spec.js new file mode 100644 index 00000000..b55b1657 --- /dev/null +++ b/test-e2e/skipOnboarding_GeneralSettingsCheck.spec.js @@ -0,0 +1,119 @@ +import { Application } from 'spectron' + +import { waitForDisappear, waitForExpectedText } from '../test-e2e/helpers/test_helpers' + +const os = require('os') +const appVersion = require('../package.json') + +let app + +const TIMEOUT = 50 * 1000 + +let app_path +const platform = os.platform() +if (platform === 'darwin') { + app_path = `./dist/mac/Ledger Live.app/Contents/MacOS/Ledger Live` +} else if (platform === 'win32') { + app_path = `./dist\\win-unpacked\\Ledger Live.exe` +} else { + app_path = `./dist/ledger-live-desktop-${appVersion.version}-linux-x86_64.AppImage` +} + +describe('Application launch', () => { + beforeEach(async () => { + app = new Application({ + path: app_path, + env: { + SKIP_ONBOARDING: '1', + }, + }) + await app.start() + }, TIMEOUT) + + afterEach(async () => { + if (app && app.isRunning()) { + await app.stop() + } + }, TIMEOUT) + + test( + 'Start app, skip onboarding, check Empty State, check General Settings and verify Developer mode', + async () => { + const title = await app.client.getTitle() + expect(title).toEqual('Ledger Live') + await app.client.waitUntilWindowLoaded() + await waitForDisappear(app, '#preload') + + // Post Onboarding (Analytics) + const analytics_title = await waitForExpectedText( + app, + '[data-e2e=onboarding_title]', + 'Analytics and bug reports', + ) + // Verify "Technical Data" + Link "Learn more" + const analytics_techData_title = await app.client.getText('[data-e2e=analytics_techData]') + expect(analytics_techData_title).toEqual('Technical data *') + await app.client.click('[data-e2e=analytics_techData_Link]') + await waitForExpectedText(app, '[data-e2e=modal_title]', 'Technical data') + await app.client.click('[data-e2e=modal_buttonClose_techData]') + analytics_title + + // Verify "Share analytics" + Link "Learn more" + const analytics_shareAnalytics_title = await app.client.getText( + '[data-e2e=analytics_shareAnalytics]', + ) + expect(analytics_shareAnalytics_title).toEqual('Share analytics') + await app.client.click('[data-e2e=analytics_shareAnalytics_Link]') + await waitForExpectedText(app, '[data-e2e=modal_title]', 'Share analytics') + await app.client.click('[data-e2e=modal_buttonClose_shareAnalytics]') + analytics_title + + // Verify "Report bugs" + const analytics_reportBugs_title = await app.client.getText('[data-e2e=analytics_reportBugs]') + expect(analytics_reportBugs_title).toEqual('Report bugs') + + await app.client.click('[data-e2e=continue_button]') + + // Finish Onboarding + await waitForExpectedText(app, '[data-e2e=finish_title]', 'Your device is ready!') + await app.client.click('[data-e2e=continue_button]') + + await waitForExpectedText(app, '[data-e2e=modal_title]', 'Trade safely') + await app.client.click('[data-e2e=continue_button]') + + // Dashboard EmptyState + await waitForExpectedText( + app, + '[data-e2e=dashboard_empty_title]', + 'Add accounts to your portfolio', + ) + const openManager_button = await app.client.getText('[data-e2e=dashboard_empty_OpenManager]') + expect(openManager_button).toEqual('Open Manager') + const addAccount_button = await app.client.getText('[data-e2e=dashboard_empty_AddAccounts]') + expect(addAccount_button).toEqual('Add accounts') + + // Open Settings + await app.client.click('[data-e2e=setting_button]') + await waitForExpectedText(app, '[data-e2e=settings_title]', 'Settings') + // Verify settings General section + const settingsGeneral_title = await app.client.getText('[data-e2e=settingsGeneral_title]') + expect(settingsGeneral_title).toEqual('General') + + // TO ADD : VERIFY PASSWORD LOCK VALUE = DISABLE ??? + // Report bugs = OFF + await app.client.click('[data-e2e=reportBugs_button]') + + // Analytics = ON + await app.client.click('[data-e2e=shareAnalytics_button]') + + // DevMode = ON + await app.client.click('[data-e2e=devMode_button]') + + // Verify Dev mode + // Add New Account + await app.client.click('[data-e2e=menuAddAccount_button]') + await waitForExpectedText(app, '[data-e2e=modal_title]', 'Add accounts') + }, + TIMEOUT, + ) +}) From f2443d676f46fd61f20553f31f733a0eb1280b2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Mon, 3 Sep 2018 17:52:49 +0200 Subject: [PATCH 17/45] Fix Clean Cache to actual clean the account operations --- src/helpers/reset.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/helpers/reset.js b/src/helpers/reset.js index 8c4fdfc5..198d354a 100644 --- a/src/helpers/reset.js +++ b/src/helpers/reset.js @@ -19,17 +19,17 @@ function reload() { } export async function hardReset() { - resetLibcoreDatabase() disableDBMiddleware() db.resetAll() await delay(500) + resetLibcoreDatabase() reload() } export async function softReset({ cleanAccountsCache }: *) { - resetLibcoreDatabase() cleanAccountsCache() await delay(500) - db.cleanCache() + await db.cleanCache() + resetLibcoreDatabase() reload() } From b4b44a733ea512dd5811507af632446e97d6d0a1 Mon Sep 17 00:00:00 2001 From: meriadec Date: Tue, 4 Sep 2018 13:37:42 +0200 Subject: [PATCH 18/45] v1.1.9 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1f85d046..9803b45b 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "productName": "Ledger Live", "description": "Ledger Live - Desktop", "repository": "https://github.com/LedgerHQ/ledger-live-desktop", - "version": "1.1.8", + "version": "1.1.9", "author": "Ledger", "license": "MIT", "scripts": { From 3581fac6841011443bd2a16240d0de7d85d94b81 Mon Sep 17 00:00:00 2001 From: meriadec Date: Tue, 4 Sep 2018 18:11:03 +0200 Subject: [PATCH 19/45] Prevent calling setState if unmounted on CurrentAddress --- src/components/CurrentAddress/index.js | 39 ++++++++++++++------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/src/components/CurrentAddress/index.js b/src/components/CurrentAddress/index.js index 488bf105..bf979fbc 100644 --- a/src/components/CurrentAddress/index.js +++ b/src/components/CurrentAddress/index.js @@ -143,7 +143,26 @@ class CurrentAddress extends PureComponent { copyFeedback: false, } - _isUnmounted = false + componentWillUnmount() { + if (this._timeout) clearTimeout(this._timeout) + } + + renderCopy = copy => { + const { t } = this.props + return ( + } + label={t('app:common.copyAddress')} + onClick={() => { + this.setState({ copyFeedback: true }) + this._timeout = setTimeout(() => this.setState({ copyFeedback: false }), 1e3) + copy() + }} + /> + ) + } + + _timeout: ?TimeoutID = null render() { const { @@ -214,23 +233,7 @@ class CurrentAddress extends PureComponent { onClick={onVerify} /> ) : null} - ( - } - label={t('app:common.copyAddress')} - onClick={() => { - this.setState({ copyFeedback: true }) - setTimeout(() => { - if (this._isUnmounted) return - this.setState({ copyFeedback: false }) - }, 1e3) - copy() - }} - /> - )} - /> +
) From 0aa78095ae1ab34337e67462a741922c24568977 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Wed, 5 Sep 2018 15:07:50 +0200 Subject: [PATCH 20/45] bump live-common 3.4.0 for temporary dgb fix --- package.json | 2 +- yarn.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 9803b45b..59bfb77b 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "@ledgerhq/hw-transport": "^4.13.0", "@ledgerhq/hw-transport-node-hid": "4.22.0", "@ledgerhq/ledger-core": "2.0.0-rc.6", - "@ledgerhq/live-common": "^3.3.0", + "@ledgerhq/live-common": "^3.4.0", "animated": "^0.2.2", "async": "^2.6.1", "axios": "^0.18.0", diff --git a/yarn.lock b/yarn.lock index 4e00a937..295b54d4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1549,9 +1549,9 @@ npm "^5.7.1" prebuild-install "^2.2.2" -"@ledgerhq/live-common@^3.3.0": - version "3.3.0" - resolved "https://registry.yarnpkg.com/@ledgerhq/live-common/-/live-common-3.3.0.tgz#e4e798f5bfee8e788094fab8dc11fe957a750544" +"@ledgerhq/live-common@^3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/live-common/-/live-common-3.4.0.tgz#49a9f8865d2e3ea898cfba15d69bdaf32c949f80" dependencies: axios "^0.18.0" bignumber.js "^7.2.1" From 6d1dac7db70b95f265b52cb6d97f67082fe888a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Wed, 5 Sep 2018 15:22:46 +0200 Subject: [PATCH 21/45] v1.1.10 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 59bfb77b..52dc402b 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "productName": "Ledger Live", "description": "Ledger Live - Desktop", "repository": "https://github.com/LedgerHQ/ledger-live-desktop", - "version": "1.1.9", + "version": "1.1.10", "author": "Ledger", "license": "MIT", "scripts": { From 3c22dbb6a41b8ca6ed4961d25b943a239b485b3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Thu, 6 Sep 2018 12:55:12 +0200 Subject: [PATCH 22/45] add migrationNonce mecanism in the walletName generation --- src/helpers/libcore.js | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/src/helpers/libcore.js b/src/helpers/libcore.js index a73e3056..754c7ec5 100644 --- a/src/helpers/libcore.js +++ b/src/helpers/libcore.js @@ -29,6 +29,15 @@ const SPLITTED_CURRENCIES = { }, } +// each time there is a breaking change that needs use to clear cache on both libcore and js side, +// we should bump a nonce in this map +// tech notes: +// - this is used to generate walletName for libcore to completely re-sync to a new wallet. +// - by changing walletName we also make the JS db refresh because we handle the accountId changes (walletName is in accountId). +const migrationNonceByCurrency = { + digibyte: 1, +} + export function isValidAddress(core: *, currency: *, address: string): boolean { const addr = new core.NJSAddress(address, currency) return addr.isValid(address, currency) @@ -319,14 +328,16 @@ const createWalletConfig = (core, configMap = {}) => { async function getOrCreateWallet( core: *, - WALLET_IDENTIFIER: string, + walletName: string, currencyId: string, isSegwit: boolean, isUnsplit: boolean, ): NJSWallet { + const migrationNonce = migrationNonceByCurrency[currencyId] + const walletId = walletName + (migrationNonce ? `_${migrationNonce}` : '') const pool = core.getPoolInstance() try { - const wallet = await timeoutTagged('getWallet', 5000, pool.getWallet(WALLET_IDENTIFIER)) + const wallet = await timeoutTagged('getWallet', 5000, pool.getWallet(walletId)) return wallet } catch (err) { const currency = await timeoutTagged('getCurrency', 5000, pool.getCurrency(currencyId)) @@ -346,7 +357,7 @@ async function getOrCreateWallet( const wallet = await timeoutTagged( 'createWallet', 10000, - core.getPoolInstance().createWallet(WALLET_IDENTIFIER, currency, njsWalletConfig), + core.getPoolInstance().createWallet(walletId, currency, njsWalletConfig), ) return wallet } @@ -513,23 +524,13 @@ export async function syncAccount({ const decodedAccountId = accountIdHelper.decode(accountId) const isSegwit = isSegwitPath(freshAddressPath) const isUnsplit = isUnsplitPath(freshAddressPath, SPLITTED_CURRENCIES[currencyId]) - let njsWallet - try { - njsWallet = await timeoutTagged( - 'getWallet', - 10000, - core.getPoolInstance().getWallet(decodedAccountId.walletName), - ) - } catch (e) { - logger.warn(`Have to reimport the account... (${e})`) - njsWallet = await getOrCreateWallet( - core, - decodedAccountId.walletName, - currencyId, - isSegwit, - isUnsplit, - ) - } + const njsWallet = await getOrCreateWallet( + core, + decodedAccountId.walletName, + currencyId, + isSegwit, + isUnsplit, + ) let njsAccount try { From abf29dda637c1915ff88b4ec82eb4aebd6883958 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Thu, 6 Sep 2018 14:43:08 +0200 Subject: [PATCH 23/45] use walletName which is diff from wallet.getName() --- src/helpers/libcore.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/helpers/libcore.js b/src/helpers/libcore.js index 754c7ec5..e044ff71 100644 --- a/src/helpers/libcore.js +++ b/src/helpers/libcore.js @@ -164,6 +164,7 @@ async function scanAccountsOnDeviceBySegwit({ const accounts = await scanNextAccount({ core, wallet, + walletName, devicePath, currencyId, accountsCount, @@ -241,6 +242,7 @@ const coreSyncAccount = (core, account) => async function scanNextAccount(props: { // $FlowFixMe wallet: NJSWallet, + walletName: string, core: *, devicePath: string, currencyId: string, @@ -256,6 +258,7 @@ async function scanNextAccount(props: { const { core, wallet, + walletName, devicePath, currencyId, accountsCount, @@ -294,6 +297,7 @@ async function scanNextAccount(props: { isUnsplit, accountIndex, wallet, + walletName, currencyId, core, ops, @@ -368,6 +372,7 @@ async function buildAccountRaw({ isSegwit, isUnsplit, wallet, + walletName, currencyId, core, accountIndex, @@ -377,6 +382,7 @@ async function buildAccountRaw({ isSegwit: boolean, isUnsplit: boolean, wallet: NJSWallet, + walletName: string, currencyId: string, accountIndex: number, core: *, @@ -441,7 +447,7 @@ async function buildAccountRaw({ type: 'libcore', version: '1', xpub, - walletName: wallet.getName(), + walletName, }), xpub, path: walletPath, @@ -522,15 +528,10 @@ export async function syncAccount({ index: number, }) { const decodedAccountId = accountIdHelper.decode(accountId) + const { walletName } = decodedAccountId const isSegwit = isSegwitPath(freshAddressPath) const isUnsplit = isUnsplitPath(freshAddressPath, SPLITTED_CURRENCIES[currencyId]) - const njsWallet = await getOrCreateWallet( - core, - decodedAccountId.walletName, - currencyId, - isSegwit, - isUnsplit, - ) + const njsWallet = await getOrCreateWallet(core, walletName, currencyId, isSegwit, isUnsplit) let njsAccount try { @@ -563,6 +564,7 @@ export async function syncAccount({ isUnsplit, accountIndex: index, wallet: njsWallet, + walletName, currencyId, core, ops, From b6e711346392d3382f5c49c4224d29c25a285026 Mon Sep 17 00:00:00 2001 From: dasilvarosa Date: Fri, 7 Sep 2018 13:47:21 +0200 Subject: [PATCH 24/45] Polish error messages and transaction verification instructions --- static/i18n/en/app.json | 4 ++-- static/i18n/en/errors.json | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/static/i18n/en/app.json b/static/i18n/en/app.json index 4861f0a8..873ea635 100644 --- a/static/i18n/en/app.json +++ b/static/i18n/en/app.json @@ -133,7 +133,7 @@ "title": "Current address", "for": "Address for account <1><0>{{accountName}}", "messageIfUnverified": "Verify the address on your device for optimal security. Press the right button to confirm.", - "messageIfAccepted": "{{currencyName}} address confirmed on your device. Carefully verify when you copy and paste it.", + "messageIfAccepted": "{{currencyName}} address confirmed. Please verify the address if you copy/paste it or if you scan the QR code.", "messageIfSkipped": "Your receive address has not been confirmed on your Ledger device. Please verify your {{currencyName}} address for optimal security." }, "deviceConnect": { @@ -315,7 +315,7 @@ }, "verification": { "title": "Verification", - "warning": "Carefully verify all transaction details now displayed on your device screen\n", + "warning": "Please verify all transaction details now displayed on your device\n", "body": "Once verified, press the right button to confirm and sign the transaction" }, "confirmation": { diff --git a/static/i18n/en/errors.json b/static/i18n/en/errors.json index c4163585..2034612e 100644 --- a/static/i18n/en/errors.json +++ b/static/i18n/en/errors.json @@ -5,7 +5,7 @@ }, "AccountNameRequired": { "title": "An account name is required", - "description": "Please provide with an account name" + "description": "Please provide an account name" }, "BtcUnmatchedApp": { "title": "That's the wrong app", @@ -80,8 +80,8 @@ "description": "Your device was locked. Please unlock it." }, "ManagerNotEnoughSpace": { - "title": "Sorry, insufficient device storage", - "description": "Uninstall some apps to increase available storage and try again." + "title": "Sorry, not enough storage left", + "description": "Please uninstall some apps to make space. This will not affect your crypto assets." }, "ManagerUninstallBTCDep": { "title": "Sorry, this app is required", From 870e8e8a31a0be9308b00bfc6521730f34c25ea0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Fri, 7 Sep 2018 13:49:44 +0200 Subject: [PATCH 25/45] Also log the POST data --- src/api/network.js | 4 +++- src/logger/logger.js | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/api/network.js b/src/api/network.js index eac84f7f..0da18166 100644 --- a/src/api/network.js +++ b/src/api/network.js @@ -6,7 +6,7 @@ import logger from 'logger' import { LedgerAPIErrorWithMessage, LedgerAPIError, NetworkDown } from 'config/errors' import anonymizer from 'helpers/anonymizer' -const userFriendlyError = (p: Promise, { url, method, startTime }): Promise => +const userFriendlyError = (p: Promise, { url, method, startTime, ...rest }): Promise => p.catch(error => { let errorToThrow if (error.response) { @@ -47,6 +47,7 @@ const userFriendlyError = (p: Promise, { url, method, startTime }): Promis }) } logger.networkError({ + ...rest, status, url, method, @@ -80,6 +81,7 @@ let implementation = (arg: Object) => { const meta = { url: arg.url, method: arg.method, + data: arg.data, startTime: Date.now(), } logger.network(meta) diff --git a/src/logger/logger.js b/src/logger/logger.js index 58eaf83b..072bf7d8 100644 --- a/src/logger/logger.js +++ b/src/logger/logger.js @@ -273,6 +273,7 @@ export default { status, error, responseTime, + ...rest }: { method: string, url: string, @@ -285,7 +286,7 @@ export default { 0, )}ms` if (logNetwork) { - logger.log('info', log, { type: 'network-error', status, method }) + logger.log('info', log, { type: 'network-error', status, method, ...rest }) } captureBreadcrumb({ category: 'network', From 7227be3e6ec88f0b862f2ffc0fb1d55627bd7bd9 Mon Sep 17 00:00:00 2001 From: meriadec Date: Fri, 7 Sep 2018 13:52:55 +0200 Subject: [PATCH 26/45] Drastically increment sync timeouts to address long sync issues --- src/config/constants.js | 2 +- src/helpers/libcore.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config/constants.js b/src/config/constants.js index cbe5b29e..06ad1b7d 100644 --- a/src/config/constants.js +++ b/src/config/constants.js @@ -45,7 +45,7 @@ export const SYNC_ALL_INTERVAL = 120 * 1000 export const SYNC_BOOT_DELAY = 2 * 1000 export const SYNC_PENDING_INTERVAL = 10 * 1000 export const SYNC_MAX_CONCURRENT = intFromEnv('LEDGER_SYNC_MAX_CONCURRENT', 1) -export const SYNC_TIMEOUT = intFromEnv('SYNC_TIMEOUT', 60 * 1000) +export const SYNC_TIMEOUT = intFromEnv('SYNC_TIMEOUT', 5 * 60 * 1000) // Endpoints... diff --git a/src/helpers/libcore.js b/src/helpers/libcore.js index e044ff71..240eba5f 100644 --- a/src/helpers/libcore.js +++ b/src/helpers/libcore.js @@ -555,7 +555,7 @@ export async function syncAccount({ unsub() const query = njsAccount.queryOperations() - const ops = await timeoutTagged('ops', 30000, query.complete().execute()) + const ops = await timeoutTagged('ops', 5 * 60 * 1000, query.complete().execute()) const njsBalance = await timeoutTagged('getBalance', 10000, njsAccount.getBalance()) const syncedRawAccount = await buildAccountRaw({ From 5deaa304136510296f78743e189701de03df019a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Mon, 10 Sep 2018 15:31:45 +0200 Subject: [PATCH 27/45] Update DGB support --- package.json | 2 +- src/components/EnsureDeviceApp.js | 6 +++--- src/config/errors.js | 1 + src/helpers/errors.js | 2 +- src/helpers/getAddressForCurrency/btc.js | 13 ++++++++++++- static/i18n/en/errors.json | 4 ++++ yarn.lock | 6 +++--- 7 files changed, 25 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 52dc402b..f82c7b48 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "@ledgerhq/hw-transport": "^4.13.0", "@ledgerhq/hw-transport-node-hid": "4.22.0", "@ledgerhq/ledger-core": "2.0.0-rc.6", - "@ledgerhq/live-common": "^3.4.0", + "@ledgerhq/live-common": "^3.5.0", "animated": "^0.2.2", "async": "^2.6.1", "axios": "^0.18.0", diff --git a/src/components/EnsureDeviceApp.js b/src/components/EnsureDeviceApp.js index 6bb0e89c..c453af7d 100644 --- a/src/components/EnsureDeviceApp.js +++ b/src/components/EnsureDeviceApp.js @@ -20,7 +20,7 @@ import IconUsb from 'icons/Usb' import type { Device } from 'types/common' -import { WrongDeviceForAccount, CantOpenDevice, BtcUnmatchedApp } from 'config/errors' +import { WrongDeviceForAccount, CantOpenDevice, UpdateYourApp } from 'config/errors' import { getCurrentDevice } from 'reducers/devices' const usbIcon = @@ -61,10 +61,10 @@ class EnsureDeviceApp extends Component<{ }, { shouldThrow: (err: Error) => { - const isWrongApp = err instanceof BtcUnmatchedApp const isWrongDevice = err instanceof WrongDeviceForAccount const isCantOpenDevice = err instanceof CantOpenDevice - return isWrongApp || isWrongDevice || isCantOpenDevice + const isUpdateYourApp = err instanceof UpdateYourApp + return isWrongDevice || isCantOpenDevice || isUpdateYourApp }, }, ) diff --git a/src/config/errors.js b/src/config/errors.js index 227e0530..f6e4a81c 100644 --- a/src/config/errors.js +++ b/src/config/errors.js @@ -33,6 +33,7 @@ export const NotEnoughBalance = createCustomErrorClass('NotEnoughBalance') export const PasswordsDontMatchError = createCustomErrorClass('PasswordsDontMatch') export const PasswordIncorrectError = createCustomErrorClass('PasswordIncorrect') export const TimeoutTagged = createCustomErrorClass('TimeoutTagged') +export const UpdateYourApp = createCustomErrorClass('UpdateYourApp') export const UserRefusedAddress = createCustomErrorClass('UserRefusedAddress') export const UserRefusedFirmwareUpdate = createCustomErrorClass('UserRefusedFirmwareUpdate') export const UserRefusedOnDevice = createCustomErrorClass('UserRefusedOnDevice') // TODO rename because it's just for transaction refusal diff --git a/src/helpers/errors.js b/src/helpers/errors.js index 5b6221ab..22436e4b 100644 --- a/src/helpers/errors.js +++ b/src/helpers/errors.js @@ -6,10 +6,10 @@ const errorClasses = {} export const createCustomErrorClass = (name: string): Class => { const C = function CustomError(message?: string, fields?: Object) { + Object.assign(this, fields) this.name = name this.message = message || name this.stack = new Error().stack - Object.assign(this, fields) } // $FlowFixMe C.prototype = new Error() diff --git a/src/helpers/getAddressForCurrency/btc.js b/src/helpers/getAddressForCurrency/btc.js index 8155a25d..69524069 100644 --- a/src/helpers/getAddressForCurrency/btc.js +++ b/src/helpers/getAddressForCurrency/btc.js @@ -3,9 +3,13 @@ import type { CryptoCurrency } from '@ledgerhq/live-common/lib/types' import Btc from '@ledgerhq/hw-app-btc' import type Transport from '@ledgerhq/hw-transport' -import { BtcUnmatchedApp } from 'config/errors' +import { BtcUnmatchedApp, UpdateYourApp } from 'config/errors' import getBitcoinLikeInfo from '../devices/getBitcoinLikeInfo' +const oldP2SH = { + digibyte: 5, +} + export default async ( transport: Transport<*>, currency: CryptoCurrency, @@ -25,6 +29,13 @@ export default async ( if (bitcoinLikeInfo) { const { P2SH, P2PKH } = await getBitcoinLikeInfo(transport) if (P2SH !== bitcoinLikeInfo.P2SH || P2PKH !== bitcoinLikeInfo.P2PKH) { + if ( + currency.id in oldP2SH && + P2SH === oldP2SH[currency.id] && + P2PKH === bitcoinLikeInfo.P2PKH + ) { + throw new UpdateYourApp(`UpdateYourApp ${currency.id}`, currency) + } throw new BtcUnmatchedApp(`BtcUnmatchedApp ${currency.id}`, currency) } } diff --git a/static/i18n/en/errors.json b/static/i18n/en/errors.json index 2034612e..043c7038 100644 --- a/static/i18n/en/errors.json +++ b/static/i18n/en/errors.json @@ -139,6 +139,10 @@ "title": "Receive address rejected", "description": "Please try again or contact Ledger Support" }, + "UpdateYourApp": { + "title": "App update required. Uninstall and reinstall the {{managerAppName}} app in the Manager", + "description": null + }, "WebsocketConnectionError": { "title": "Sorry, try again (websocket error).", "description": null diff --git a/yarn.lock b/yarn.lock index 295b54d4..0e14d6cc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1549,9 +1549,9 @@ npm "^5.7.1" prebuild-install "^2.2.2" -"@ledgerhq/live-common@^3.4.0": - version "3.4.0" - resolved "https://registry.yarnpkg.com/@ledgerhq/live-common/-/live-common-3.4.0.tgz#49a9f8865d2e3ea898cfba15d69bdaf32c949f80" +"@ledgerhq/live-common@^3.5.0": + version "3.5.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/live-common/-/live-common-3.5.0.tgz#3176f5f3cdbe600775edd2b5dac5c10addd90e6f" dependencies: axios "^0.18.0" bignumber.js "^7.2.1" From 65bb796d453719c049f2b28c2a3e84a82379c093 Mon Sep 17 00:00:00 2001 From: meriadec Date: Sun, 9 Sep 2018 17:20:54 +0200 Subject: [PATCH 28/45] Add DevTools page and ability to import accounts via xpub --- src/commands/index.js | 2 + src/commands/libcoreScanFromXPUB.js | 29 +++ .../DevToolsPage/AccountImporter.js | 207 ++++++++++++++++++ src/components/DevToolsPage/index.js | 11 + src/components/MainSideBar/index.js | 32 ++- src/components/base/Input/index.js | 2 +- src/components/layout/Default.js | 2 + src/config/cryptocurrencies.js | 9 + src/helpers/libcore.js | 77 +++++-- static/i18n/en/app.json | 3 +- 10 files changed, 356 insertions(+), 18 deletions(-) create mode 100644 src/commands/libcoreScanFromXPUB.js create mode 100644 src/components/DevToolsPage/AccountImporter.js create mode 100644 src/components/DevToolsPage/index.js diff --git a/src/commands/index.js b/src/commands/index.js index 8b1ff369..117a8b3f 100644 --- a/src/commands/index.js +++ b/src/commands/index.js @@ -18,6 +18,7 @@ import isDashboardOpen from 'commands/isDashboardOpen' import libcoreGetFees from 'commands/libcoreGetFees' import libcoreGetVersion from 'commands/libcoreGetVersion' import libcoreScanAccounts from 'commands/libcoreScanAccounts' +import libcoreScanFromXPUB from 'commands/libcoreScanFromXPUB' import libcoreSignAndBroadcast from 'commands/libcoreSignAndBroadcast' import libcoreSyncAccount from 'commands/libcoreSyncAccount' import libcoreValidAddress from 'commands/libcoreValidAddress' @@ -49,6 +50,7 @@ const all: Array> = [ libcoreGetFees, libcoreGetVersion, libcoreScanAccounts, + libcoreScanFromXPUB, libcoreSignAndBroadcast, libcoreSyncAccount, libcoreValidAddress, diff --git a/src/commands/libcoreScanFromXPUB.js b/src/commands/libcoreScanFromXPUB.js new file mode 100644 index 00000000..1c595861 --- /dev/null +++ b/src/commands/libcoreScanFromXPUB.js @@ -0,0 +1,29 @@ +// @flow + +import { fromPromise } from 'rxjs/observable/fromPromise' +import type { AccountRaw } from '@ledgerhq/live-common/lib/types' + +import { createCommand, Command } from 'helpers/ipc' +import withLibcore from 'helpers/withLibcore' +import { scanAccountsFromXPUB } from 'helpers/libcore' + +type Input = { + currencyId: string, + xpub: string, + isSegwit: boolean, + isUnsplit: boolean, +} + +type Result = AccountRaw + +const cmd: Command = createCommand( + 'libcoreScanFromXPUB', + ({ currencyId, xpub, isSegwit, isUnsplit }) => + fromPromise( + withLibcore(async core => + scanAccountsFromXPUB({ core, currencyId, xpub, isSegwit, isUnsplit }), + ), + ), +) + +export default cmd diff --git a/src/components/DevToolsPage/AccountImporter.js b/src/components/DevToolsPage/AccountImporter.js new file mode 100644 index 00000000..88afd7c9 --- /dev/null +++ b/src/components/DevToolsPage/AccountImporter.js @@ -0,0 +1,207 @@ +// @flow + +import React, { PureComponent, Fragment } from 'react' +import invariant from 'invariant' +import { connect } from 'react-redux' + +import type { Currency, Account } from '@ledgerhq/live-common/lib/types' + +import { decodeAccount } from 'reducers/accounts' +import { addAccount } from 'actions/accounts' + +import FormattedVal from 'components/base/FormattedVal' +import Switch from 'components/base/Switch' +import Spinner from 'components/base/Spinner' +import Box, { Card } from 'components/base/Box' +import TranslatedError from 'components/TranslatedError' +import Button from 'components/base/Button' +import Input from 'components/base/Input' +import Label from 'components/base/Label' +import SelectCurrency from 'components/SelectCurrency' +import { CurrencyCircleIcon } from 'components/base/CurrencyBadge' + +import { idleCallback } from 'helpers/promise' +import { splittedCurrencies } from 'config/cryptocurrencies' + +import scanFromXPUB from 'commands/libcoreScanFromXPUB' + +const mapDispatchToProps = { + addAccount, +} + +type Props = { + addAccount: Account => void, +} + +const INITIAL_STATE = { + status: 'idle', + currency: null, + xpub: '', + account: null, + isSegwit: true, + isUnsplit: false, + error: null, +} + +type State = { + status: string, + currency: ?Currency, + xpub: string, + account: ?Account, + isSegwit: boolean, + isUnsplit: boolean, + error: ?Error, +} + +class AccountImporter extends PureComponent { + state = INITIAL_STATE + + onChangeCurrency = currency => { + if (currency.family !== 'bitcoin') return + this.setState({ + currency, + isSegwit: !!currency.supportsSegwit, + isUnsplit: false, + }) + } + + onChangeXPUB = xpub => this.setState({ xpub }) + onChangeSegwit = isSegwit => this.setState({ isSegwit }) + onChangeUnsplit = isUnsplit => this.setState({ isUnsplit }) + + isValid = () => { + const { currency, xpub } = this.state + return !!currency && !!xpub + } + + scan = async () => { + if (!this.isValid()) return + this.setState({ status: 'scanning' }) + try { + const { currency, xpub, isSegwit, isUnsplit } = this.state + invariant(currency, 'no currency') + const rawAccount = await scanFromXPUB + .send({ + currencyId: currency.id, + xpub, + isSegwit, + isUnsplit, + }) + .toPromise() + const account = decodeAccount(rawAccount) + this.setState({ status: 'finish', account }) + } catch (error) { + this.setState({ status: 'error', error }) + } + } + + import = async () => { + const { account } = this.state + invariant(account, 'no account') + await idleCallback() + this.props.addAccount(account) + this.reset() + } + + reset = () => this.setState(INITIAL_STATE) + + render() { + const { currency, xpub, isSegwit, isUnsplit, status, account, error } = this.state + const supportsSplit = !!currency && !!splittedCurrencies[currency.id] + return ( + + {status === 'idle' ? ( + + + + + + {currency && (currency.supportsSegwit || supportsSplit) ? ( + + {supportsSplit && ( + + + {'unsplit'} + + + + )} + {currency.supportsSegwit && ( + + + {'segwit'} + + + + )} + + ) : null} + + + + + + + + + ) : status === 'scanning' ? ( + + + + ) : status === 'finish' ? ( + account ? ( + + + {currency && } + + {account.name} + + {`${account.operations.length} operation(s)`} + + + + + + ) : ( + + {'No accounts found or wrong xpub'} + + + ) + ) : status === 'error' ? ( + + + + + + + ) : null} + + ) + } +} + +export default connect( + null, + mapDispatchToProps, +)(AccountImporter) diff --git a/src/components/DevToolsPage/index.js b/src/components/DevToolsPage/index.js new file mode 100644 index 00000000..bc849837 --- /dev/null +++ b/src/components/DevToolsPage/index.js @@ -0,0 +1,11 @@ +import React from 'react' + +import Box from 'components/base/Box' + +import AccountImporter from './AccountImporter' + +export default () => ( + + + +) diff --git a/src/components/MainSideBar/index.js b/src/components/MainSideBar/index.js index 1c67e87f..e1cb9051 100644 --- a/src/components/MainSideBar/index.js +++ b/src/components/MainSideBar/index.js @@ -19,6 +19,7 @@ import { i } from 'helpers/staticPath' import { accountsSelector } from 'reducers/accounts' import { openModal } from 'reducers/modals' import { getUpdateStatus } from 'reducers/update' +import { developerModeSelector } from 'reducers/settings' import { SideBarList, SideBarListItem } from 'components/base/SideBar' import Box from 'components/base/Box' @@ -38,6 +39,7 @@ import TopGradient from './TopGradient' const mapStateToProps = state => ({ accounts: accountsSelector(state), updateStatus: getUpdateStatus(state), + developerMode: developerModeSelector(state), }) const mapDispatchToProps = { @@ -52,8 +54,26 @@ type Props = { push: string => void, openModal: string => void, updateStatus: UpdateStatus, + developerMode: boolean, } +const IconDev = () => ( +
+ {'DEV'} +
+) + class MainSideBar extends PureComponent { push = (to: string) => { const { push } = this.props @@ -78,10 +98,11 @@ class MainSideBar extends PureComponent { handleOpenReceiveModal = () => this.props.openModal(MODAL_RECEIVE) handleClickManager = () => this.push('/manager') handleClickExchange = () => this.push('/exchange') + handleClickDev = () => this.push('/dev') handleOpenImportModal = () => this.props.openModal(MODAL_ADD_ACCOUNTS) render() { - const { t, accounts, location, updateStatus } = this.props + const { t, accounts, location, updateStatus, developerMode } = this.props const { pathname } = location const addAccountButton = ( @@ -133,6 +154,15 @@ class MainSideBar extends PureComponent { onClick={this.handleClickExchange} isActive={pathname === '/exchange'} /> + {developerMode && ( + + )} ) => void, onChange?: Function, - onEnter?: (SyntheticKeyboardEvent) => void, + onEnter?: (SyntheticKeyboardEvent) => *, onEsc?: (SyntheticKeyboardEvent) => void, onFocus: (SyntheticInputEvent) => void, renderLeft?: any, diff --git a/src/components/layout/Default.js b/src/components/layout/Default.js index 5d70f1db..ca47557f 100644 --- a/src/components/layout/Default.js +++ b/src/components/layout/Default.js @@ -19,6 +19,7 @@ import AccountPage from 'components/AccountPage' import DashboardPage from 'components/DashboardPage' import ManagerPage from 'components/ManagerPage' import ExchangePage from 'components/ExchangePage' +import DevToolsPage from 'components/DevToolsPage' import SettingsPage from 'components/SettingsPage' import KeyboardContent from 'components/KeyboardContent' import PerfIndicator from 'components/PerfIndicator' @@ -110,6 +111,7 @@ class Default extends Component { +
diff --git a/src/config/cryptocurrencies.js b/src/config/cryptocurrencies.js index 2667ddb7..21f56322 100644 --- a/src/config/cryptocurrencies.js +++ b/src/config/cryptocurrencies.js @@ -35,3 +35,12 @@ export const listCryptoCurrencies = memoize((withDevCrypto?: boolean) => .filter(c => supported.includes(c.id)) .sort((a, b) => a.name.localeCompare(b.name)), ) + +export const splittedCurrencies = { + bitcoin_cash: { + coinType: 0, + }, + bitcoin_gold: { + coinType: 0, + }, +} diff --git a/src/helpers/libcore.js b/src/helpers/libcore.js index 240eba5f..5f7f20e5 100644 --- a/src/helpers/libcore.js +++ b/src/helpers/libcore.js @@ -15,20 +15,11 @@ import type { NJSAccount, NJSOperation } from '@ledgerhq/ledger-core/src/ledgerc import { isSegwitPath, isUnsplitPath } from 'helpers/bip32' import * as accountIdHelper from 'helpers/accountId' import { NoAddressesFound } from 'config/errors' +import { splittedCurrencies } from 'config/cryptocurrencies' import { deserializeError } from './errors' import { getAccountPlaceholderName, getNewAccountPlaceholderName } from './accountName' import { timeoutTagged } from './promise' -// TODO: put that info inside currency itself -const SPLITTED_CURRENCIES = { - bitcoin_cash: { - coinType: 0, - }, - bitcoin_gold: { - coinType: 0, - }, -} - // each time there is a breaking change that needs use to clear cache on both libcore and js side, // we should bump a nonce in this map // tech notes: @@ -84,7 +75,7 @@ export async function scanAccountsOnDevice(props: Props): Promise } // TODO: put that info inside currency itself - if (currencyId in SPLITTED_CURRENCIES) { + if (currencyId in splittedCurrencies) { const splittedAccounts = await scanAccountsOnDeviceBySegwit({ ...commonParams, isSegwit: false, @@ -118,7 +109,7 @@ function encodeWalletName({ isSegwit: boolean, isUnsplit: boolean, }) { - const splitConfig = isUnsplit ? SPLITTED_CURRENCIES[currencyId] || null : null + const splitConfig = isUnsplit ? splittedCurrencies[currencyId] || null : null return `${publicKey}__${currencyId}${isSegwit ? '_segwit' : ''}${splitConfig ? '_unsplit' : ''}` } @@ -142,7 +133,7 @@ async function scanAccountsOnDeviceBySegwit({ isUnsplit: boolean, }): Promise { const customOpts = - isUnsplit && SPLITTED_CURRENCIES[currencyId] ? SPLITTED_CURRENCIES[currencyId] : null + isUnsplit && splittedCurrencies[currencyId] ? splittedCurrencies[currencyId] : null const { coinType } = customOpts ? customOpts.coinType : getCryptoCurrencyById(currencyId) const path = `${isSegwit ? '49' : '44'}'/${coinType}'` @@ -345,7 +336,7 @@ async function getOrCreateWallet( return wallet } catch (err) { const currency = await timeoutTagged('getCurrency', 5000, pool.getCurrency(currencyId)) - const splitConfig = isUnsplit ? SPLITTED_CURRENCIES[currencyId] || null : null + const splitConfig = isUnsplit ? splittedCurrencies[currencyId] || null : null const coinType = splitConfig ? splitConfig.coinType : '' const walletConfig = isSegwit ? { @@ -530,7 +521,7 @@ export async function syncAccount({ const decodedAccountId = accountIdHelper.decode(accountId) const { walletName } = decodedAccountId const isSegwit = isSegwitPath(freshAddressPath) - const isUnsplit = isUnsplitPath(freshAddressPath, SPLITTED_CURRENCIES[currencyId]) + const isUnsplit = isUnsplitPath(freshAddressPath, splittedCurrencies[currencyId]) const njsWallet = await getOrCreateWallet(core, walletName, currencyId, isSegwit, isUnsplit) let njsAccount @@ -584,3 +575,59 @@ export function libcoreAmountToBigNumber(njsAmount: *): BigNumber { export function bigNumberToLibcoreAmount(core: *, njsWalletCurrency: *, bigNumber: BigNumber) { return new core.NJSAmount(njsWalletCurrency, 0).fromHex(njsWalletCurrency, bigNumber.toString(16)) } + +export async function scanAccountsFromXPUB({ + core, + currencyId, + xpub, + isSegwit, + isUnsplit, +}: { + core: *, + currencyId: string, + xpub: string, + isSegwit: boolean, + isUnsplit: boolean, +}) { + const currency = getCryptoCurrencyById(currencyId) + const walletName = encodeWalletName({ + publicKey: `debug_${xpub}`, + currencyId, + isSegwit, + isUnsplit, + }) + + const wallet = await getOrCreateWallet(core, walletName, currencyId, isSegwit, isUnsplit) + + await wallet.eraseDataSince(new Date(0)) + + const index = 0 + + const extendedInfos = { + index, + owners: ['main'], + derivations: [ + `${isSegwit ? '49' : '44'}'/${currency.coinType}'`, + `${isSegwit ? '49' : '44'}'/${currency.coinType}'/0`, + ], + extendedKeys: [xpub], + } + + const account = await wallet.newAccountWithExtendedKeyInfo(extendedInfos) + await coreSyncAccount(core, account) + const query = account.queryOperations() + const ops = await query.complete().execute() + const rawAccount = await buildAccountRaw({ + njsAccount: account, + isSegwit, + isUnsplit, + accountIndex: index, + wallet, + walletName, + currencyId, + core, + ops, + }) + + return rawAccount +} diff --git a/static/i18n/en/app.json b/static/i18n/en/app.json index 873ea635..b94db001 100644 --- a/static/i18n/en/app.json +++ b/static/i18n/en/app.json @@ -78,7 +78,8 @@ "menu": "Menu", "accounts": "Accounts ({{count}})", "manager": "Manager", - "exchange": "Buy/Trade" + "exchange": "Buy/Trade", + "developer": "Dev tools" }, "account": { "lastOperations": "Last operations", From 92dd2d95287a7c3ad9f5411b092ca31c881e9bcd Mon Sep 17 00:00:00 2001 From: meriadec Date: Mon, 10 Sep 2018 17:29:50 +0200 Subject: [PATCH 29/45] Bump ledger-core to v2.0.0-rc.7 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f82c7b48..c4ff09fe 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "@ledgerhq/hw-app-xrp": "^4.13.0", "@ledgerhq/hw-transport": "^4.13.0", "@ledgerhq/hw-transport-node-hid": "4.22.0", - "@ledgerhq/ledger-core": "2.0.0-rc.6", + "@ledgerhq/ledger-core": "2.0.0-rc.7", "@ledgerhq/live-common": "^3.5.0", "animated": "^0.2.2", "async": "^2.6.1", From c164dbee82438caeccb9bdac141d85d1ef7e61f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Mon, 10 Sep 2018 17:48:31 +0200 Subject: [PATCH 30/45] use libcore inputs and outputs to return the optimistic update --- src/commands/libcoreSignAndBroadcast.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/commands/libcoreSignAndBroadcast.js b/src/commands/libcoreSignAndBroadcast.js index e479c9f3..83d36a2b 100644 --- a/src/commands/libcoreSignAndBroadcast.js +++ b/src/commands/libcoreSignAndBroadcast.js @@ -164,7 +164,6 @@ export async function doSignAndBroadcast({ accountId, currencyId, xpub, - freshAddress, freshAddressPath, index, transaction, @@ -237,6 +236,16 @@ export async function doSignAndBroadcast({ .asBitcoinLikeAccount() .broadcastRawTransaction(Array.from(Buffer.from(signedTransaction, 'hex'))) + const senders = builded + .getInputs() + .map(input => input.getAddress()) + .filter(a => a) + + const recipients = builded + .getOutputs() + .map(output => output.getAddress()) + .filter(a => a) + const fee = libcoreAmountToBigNumber(builded.getFees()) // NB we don't check isCancelled() because the broadcast is not cancellable now! @@ -250,9 +259,8 @@ export async function doSignAndBroadcast({ fee: fee.toString(), blockHash: null, blockHeight: null, - // FIXME for senders and recipients, can we ask the libcore? - senders: [freshAddress], - recipients: [transaction.recipient], + senders, + recipients, accountId, date: new Date().toISOString(), }) From 354d5bd5f02db2e96c625899dbb3d27ca3a07ff1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Mon, 10 Sep 2018 17:29:49 +0200 Subject: [PATCH 31/45] Fix checking the tagId validity --- src/components/AdvancedOptions/RippleKind.js | 58 ++++++++++++-------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/src/components/AdvancedOptions/RippleKind.js b/src/components/AdvancedOptions/RippleKind.js index b87cff5f..74b0b5a4 100644 --- a/src/components/AdvancedOptions/RippleKind.js +++ b/src/components/AdvancedOptions/RippleKind.js @@ -1,5 +1,6 @@ // @flow -import React from 'react' +import React, { Component } from 'react' +import { BigNumber } from 'bignumber.js' import { translate } from 'react-i18next' import Box from 'components/base/Box' @@ -13,24 +14,37 @@ type Props = { t: *, } -export default translate()(({ tag, onChangeTag, t }: Props) => ( - - - - - - - { - const tag = parseInt(str, 10) - if (!isNaN(tag) && isFinite(tag)) onChangeTag(tag) - else onChangeTag(undefined) - }} - /> - - - -)) +const uint32maxPlus1 = BigNumber(2).pow(32) + +class RippleKind extends Component { + onChange = str => { + const { onChangeTag } = this.props + const tag = BigNumber(str.replace(/[^0-9]/g, '')) + if (!tag.isNaN() && tag.isFinite()) { + if (tag.isInteger() && tag.isPositive() && tag.lt(uint32maxPlus1)) { + onChangeTag(tag.toNumber()) + } + } else { + onChangeTag(undefined) + } + } + render() { + const { tag, t } = this.props + return ( + + + + + + + + + + + ) + } +} + +export default translate()(RippleKind) From 573dca8e6664b58383cfa723fe7a275d5ad89af8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Mon, 10 Sep 2018 18:12:16 +0200 Subject: [PATCH 32/45] update lib --- package.json | 2 +- yarn.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index c4ff09fe..2b3bc8b5 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "@ledgerhq/hw-transport": "^4.13.0", "@ledgerhq/hw-transport-node-hid": "4.22.0", "@ledgerhq/ledger-core": "2.0.0-rc.7", - "@ledgerhq/live-common": "^3.5.0", + "@ledgerhq/live-common": "^3.5.1", "animated": "^0.2.2", "async": "^2.6.1", "axios": "^0.18.0", diff --git a/yarn.lock b/yarn.lock index 0e14d6cc..71c73832 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1536,9 +1536,9 @@ dependencies: events "^2.0.0" -"@ledgerhq/ledger-core@2.0.0-rc.6": - version "2.0.0-rc.6" - resolved "https://registry.yarnpkg.com/@ledgerhq/ledger-core/-/ledger-core-2.0.0-rc.6.tgz#a08c84bd91c680cd731e1030ce081a9b568a2c47" +"@ledgerhq/ledger-core@2.0.0-rc.7": + version "2.0.0-rc.7" + resolved "https://registry.yarnpkg.com/@ledgerhq/ledger-core/-/ledger-core-2.0.0-rc.7.tgz#36a2573f01a1e19c51c6e39692e6b0f8be6c3a77" dependencies: "@ledgerhq/hw-app-btc" "^4.7.3" "@ledgerhq/hw-transport-node-hid" "^4.7.6" @@ -1549,9 +1549,9 @@ npm "^5.7.1" prebuild-install "^2.2.2" -"@ledgerhq/live-common@^3.5.0": - version "3.5.0" - resolved "https://registry.yarnpkg.com/@ledgerhq/live-common/-/live-common-3.5.0.tgz#3176f5f3cdbe600775edd2b5dac5c10addd90e6f" +"@ledgerhq/live-common@^3.5.1": + version "3.5.1" + resolved "https://registry.yarnpkg.com/@ledgerhq/live-common/-/live-common-3.5.1.tgz#dab3eb061f361999a9e04ef564808831faac61ea" dependencies: axios "^0.18.0" bignumber.js "^7.2.1" From 187e21f099c2358eaa638d4a5fb31b57b348b20c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Mon, 10 Sep 2018 19:03:29 +0200 Subject: [PATCH 33/45] Fix using getOrCreateWallet for all access to getWallet --- src/bridge/LibcoreBridge.js | 5 ++--- src/commands/libcoreGetFees.js | 28 ++++++++++++++++++++++--- src/commands/libcoreSignAndBroadcast.js | 14 ++++++++++--- src/helpers/libcore.js | 2 +- 4 files changed, 39 insertions(+), 10 deletions(-) diff --git a/src/bridge/LibcoreBridge.js b/src/bridge/LibcoreBridge.js index 642788d1..c9b9535d 100644 --- a/src/bridge/LibcoreBridge.js +++ b/src/bridge/LibcoreBridge.js @@ -9,7 +9,7 @@ import FeesBitcoinKind from 'components/FeesField/BitcoinKind' import libcoreScanAccounts from 'commands/libcoreScanAccounts' import libcoreSyncAccount from 'commands/libcoreSyncAccount' import libcoreSignAndBroadcast from 'commands/libcoreSignAndBroadcast' -import libcoreGetFees from 'commands/libcoreGetFees' +import libcoreGetFees, { extractGetFeesInputFromAccount } from 'commands/libcoreGetFees' import libcoreValidAddress from 'commands/libcoreValidAddress' import { NotEnoughBalance } from 'config/errors' import type { WalletBridge, EditProps } from './types' @@ -85,8 +85,7 @@ const getFees = async (a, transaction) => { if (promise) return promise promise = libcoreGetFees .send({ - accountId: a.id, - accountIndex: a.index, + ...extractGetFeesInputFromAccount(a), transaction: serializeTransaction(transaction), }) .toPromise() diff --git a/src/commands/libcoreGetFees.js b/src/commands/libcoreGetFees.js index ab96ff30..58e8512c 100644 --- a/src/commands/libcoreGetFees.js +++ b/src/commands/libcoreGetFees.js @@ -4,9 +4,17 @@ import { Observable } from 'rxjs' import { BigNumber } from 'bignumber.js' import withLibcore from 'helpers/withLibcore' import { createCommand, Command } from 'helpers/ipc' +import type { Account } from '@ledgerhq/live-common/lib/types' import * as accountIdHelper from 'helpers/accountId' -import { isValidAddress, libcoreAmountToBigNumber, bigNumberToLibcoreAmount } from 'helpers/libcore' +import { + isValidAddress, + libcoreAmountToBigNumber, + bigNumberToLibcoreAmount, + getOrCreateWallet, +} from 'helpers/libcore' +import { isSegwitPath, isUnsplitPath } from 'helpers/bip32' import { InvalidAddress } from 'config/errors' +import { splittedCurrencies } from 'config/cryptocurrencies' type BitcoinLikeTransaction = { // TODO we rename this Transaction concept into transactionInput @@ -19,20 +27,34 @@ type Input = { accountId: string, accountIndex: number, transaction: BitcoinLikeTransaction, + currencyId: string, + isSegwit: boolean, + isUnsplit: boolean, +} + +export const extractGetFeesInputFromAccount = (a: Account) => { + const currencyId = a.currency.id + return { + accountId: a.id, + accountIndex: a.index, + currencyId, + isSegwit: isSegwitPath(a.freshAddressPath), + isUnsplit: isUnsplitPath(a.freshAddressPath, splittedCurrencies[currencyId]), + } } type Result = { totalFees: string } const cmd: Command = createCommand( 'libcoreGetFees', - ({ accountId, accountIndex, transaction }) => + ({ accountId, currencyId, isSegwit, isUnsplit, accountIndex, transaction }) => Observable.create(o => { let unsubscribed = false const isCancelled = () => unsubscribed withLibcore(async core => { const { walletName } = accountIdHelper.decode(accountId) - const njsWallet = await core.getPoolInstance().getWallet(walletName) + const njsWallet = await getOrCreateWallet(core, walletName, currencyId, isSegwit, isUnsplit) if (isCancelled()) return const njsAccount = await njsWallet.getAccount(accountIndex) if (isCancelled()) return diff --git a/src/commands/libcoreSignAndBroadcast.js b/src/commands/libcoreSignAndBroadcast.js index 83d36a2b..d1fbd814 100644 --- a/src/commands/libcoreSignAndBroadcast.js +++ b/src/commands/libcoreSignAndBroadcast.js @@ -6,8 +6,13 @@ import type { OperationRaw } from '@ledgerhq/live-common/lib/types' import Btc from '@ledgerhq/hw-app-btc' import { Observable } from 'rxjs' import { getCryptoCurrencyById } from '@ledgerhq/live-common/lib/helpers/currencies' -import { isSegwitPath } from 'helpers/bip32' -import { libcoreAmountToBigNumber, bigNumberToLibcoreAmount } from 'helpers/libcore' +import { isSegwitPath, isUnsplitPath } from 'helpers/bip32' +import { + libcoreAmountToBigNumber, + bigNumberToLibcoreAmount, + getOrCreateWallet, +} from 'helpers/libcore' +import { splittedCurrencies } from 'config/cryptocurrencies' import withLibcore from 'helpers/withLibcore' import { createCommand, Command } from 'helpers/ipc' @@ -187,7 +192,10 @@ export async function doSignAndBroadcast({ onOperationBroadcasted: (optimisticOp: $Exact) => void, }): Promise { const { walletName } = accountIdHelper.decode(accountId) - const njsWallet = await core.getPoolInstance().getWallet(walletName) + + const isSegwit = isSegwitPath(freshAddressPath) + const isUnsplit = isUnsplitPath(freshAddressPath, splittedCurrencies[currencyId]) + const njsWallet = await getOrCreateWallet(core, walletName, currencyId, isSegwit, isUnsplit) if (isCancelled()) return const njsAccount = await njsWallet.getAccount(index) if (isCancelled()) return diff --git a/src/helpers/libcore.js b/src/helpers/libcore.js index 5f7f20e5..395b3b64 100644 --- a/src/helpers/libcore.js +++ b/src/helpers/libcore.js @@ -321,7 +321,7 @@ const createWalletConfig = (core, configMap = {}) => { return config } -async function getOrCreateWallet( +export async function getOrCreateWallet( core: *, walletName: string, currencyId: string, From f073039dc045550a91ccc35a9deed365d647567a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Tue, 11 Sep 2018 08:08:12 +0200 Subject: [PATCH 34/45] Use SYNC_TIMEOUT in query.complete().execute() too --- src/helpers/libcore.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/helpers/libcore.js b/src/helpers/libcore.js index 5f7f20e5..8be72a98 100644 --- a/src/helpers/libcore.js +++ b/src/helpers/libcore.js @@ -7,7 +7,7 @@ import { BigNumber } from 'bignumber.js' import Btc from '@ledgerhq/hw-app-btc' import { withDevice } from 'helpers/deviceAccess' import { getCryptoCurrencyById } from '@ledgerhq/live-common/lib/helpers/currencies' -import { SHOW_LEGACY_NEW_ACCOUNT } from 'config/constants' +import { SHOW_LEGACY_NEW_ACCOUNT, SYNC_TIMEOUT } from 'config/constants' import type { AccountRaw, OperationRaw, OperationType } from '@ledgerhq/live-common/lib/types' import type { NJSAccount, NJSOperation } from '@ledgerhq/ledger-core/src/ledgercore_doc' @@ -546,7 +546,7 @@ export async function syncAccount({ unsub() const query = njsAccount.queryOperations() - const ops = await timeoutTagged('ops', 5 * 60 * 1000, query.complete().execute()) + const ops = await timeoutTagged('ops', SYNC_TIMEOUT, query.complete().execute()) const njsBalance = await timeoutTagged('getBalance', 10000, njsAccount.getBalance()) const syncedRawAccount = await buildAccountRaw({ From b03195c545b32b59be403c04440324361ba8ed49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Tue, 11 Sep 2018 13:04:43 +0200 Subject: [PATCH 35/45] Disable Ripple & ZenCash install (now XRP and Horizen) --- src/components/ManagerPage/AppsList.js | 5 ++++- src/components/ManagerPage/ManagerApp.js | 26 +++++++++++++----------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/components/ManagerPage/AppsList.js b/src/components/ManagerPage/AppsList.js index e2f8bf1c..f589ea7f 100644 --- a/src/components/ManagerPage/AppsList.js +++ b/src/components/ManagerPage/AppsList.js @@ -71,6 +71,9 @@ type State = { mode: Mode, } +const oldAppsInstallDisabled = ['ZenCash', 'Ripple'] +const canHandleInstall = c => !oldAppsInstallDisabled.includes(c.name) + const LoadingApp = () => ( @@ -285,7 +288,7 @@ class AppsList extends PureComponent { name={c.name} version={`Version ${c.version}`} icon={ICONS_FALLBACK[c.icon] || c.icon} - onInstall={this.handleInstallApp(c)} + onInstall={canHandleInstall(c) ? this.handleInstallApp(c) : null} onUninstall={this.handleUninstallApp(c)} /> ))} diff --git a/src/components/ManagerPage/ManagerApp.js b/src/components/ManagerPage/ManagerApp.js index 9f4b4494..eb50b073 100644 --- a/src/components/ManagerPage/ManagerApp.js +++ b/src/components/ManagerPage/ManagerApp.js @@ -49,7 +49,7 @@ type Props = { name: string, version: string, icon: string, - onInstall: Function, + onInstall?: Function, onUninstall: Function, } @@ -64,17 +64,19 @@ function ManagerApp({ name, version, icon, onInstall, onUninstall, t }: Props) { {version} - + {onInstall ? ( + + ) : null}