diff --git a/.circleci/config.yml b/.circleci/config.yml index c9faa4ea..4ef83dc7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -9,7 +9,7 @@ jobs: build: <<: *defaults steps: - - run: sudo apt-get install -y libudev-dev + - run: sudo apt-get install -y libudev-dev libfuse-dev - run: name: Install latest yarn command: | diff --git a/.gitignore b/.gitignore index c76923df..655feaed 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +xliffs/ /.env /dist/ /flow-typed/ diff --git a/package.json b/package.json index c866ccbb..2f5f1a8b 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.2.1", + "version": "1.2.3", "author": "Ledger", "license": "MIT", "scripts": { @@ -35,13 +35,13 @@ } }, "dependencies": { - "@ledgerhq/hw-app-btc": "4.24.0", + "@ledgerhq/hw-app-btc": "^4.27.0", "@ledgerhq/hw-app-eth": "^4.24.0", - "@ledgerhq/hw-app-xrp": "^4.24.0", + "@ledgerhq/hw-app-xrp": "^4.25.0", "@ledgerhq/hw-transport": "^4.24.0", "@ledgerhq/hw-transport-node-hid": "4.24.0", - "@ledgerhq/ledger-core": "2.0.0-rc.8", - "@ledgerhq/live-common": "4.0.0-beta.1", + "@ledgerhq/ledger-core": "2.0.0-rc.11", + "@ledgerhq/live-common": "4.3.0", "animated": "^0.2.2", "async": "^2.6.1", "axios": "^0.18.0", @@ -74,7 +74,7 @@ "qs": "^6.5.1", "raven": "^2.5.0", "raven-js": "^3.24.2", - "react": "^16.4.1", + "react": "^16.6.1", "react-dom": "^16.4.1", "react-i18next": "^7.7.0", "react-key-handler": "^1.0.1", diff --git a/scripts/download-xliffs.sh b/scripts/download-xliffs.sh new file mode 100755 index 00000000..62418211 --- /dev/null +++ b/scripts/download-xliffs.sh @@ -0,0 +1,17 @@ +#!/bin/sh + + +if [ -z "$CROWDIN_TOKEN" ]; then + echo "CROWDIN_TOKEN env required" >&2 + exit 1 +fi + +rm -rf xliffs +mkdir xliffs +cd xliffs + +for lang in fr es-ES zh-CN ja ko ru; do + curl "https://api.crowdin.com/api/project/ledger-wallet/export-file?file=develop/static/i18n/en/app.json&language=$lang&format=xliff&key=$CROWDIN_TOKEN" > en-$lang.xliff +done + +zip -r ledger-live-langs.zip *.xliff diff --git a/scripts/release.sh b/scripts/release.sh index 14a573e9..34a31fcb 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -69,48 +69,58 @@ fi runJob "yarn compile" "compiling..." "compiled" "failed to compile" "verbose" -# -------------------------------------------------------------------- -# Linux: Internal process error (null) -# -# context: https://github.com/LedgerHQ/ledger-live-desktop/issues/1010 -# Linux: Internal process error (null) -# -# The "fix" is not optimal, as it doesn't really solve the problem -# (electron loading system openssl before we can load our embedded one) -# Quick summary: -# -# - build without publishing -# - unpack the .AppImage -# - download reported working libs from ubuntu mirrors, put it inside -# - re-pack the .AppImage -# - checksum stuff -# - upload to gh - -runJob \ - "DEBUG=electron-builder electron-builder build --publish never" \ - "building and packaging app..." \ - "app built and packaged successfully" \ - "failed to build app" \ - "verbose" - -runJob \ - "scripts/patch-appimage.sh" \ - "patching AppImage..." \ - "AppImage patched successfully" \ - "failed to patch AppImage" - -LEDGER_LIVE_VERSION=$(grep version package.json | sed -E 's/.*: "(.*)",/\1/g') - -scripts/upload-github-release-asset.sh \ - github_api_token="$GH_TOKEN" \ - owner=LedgerHQ \ - repo=ledger-live-desktop \ - tag="$GH_TAG" \ - filename="dist/ledger-live-desktop-$LEDGER_LIVE_VERSION-linux-x86_64.AppImage" - -scripts/upload-github-release-asset.sh \ - github_api_token="$GH_TOKEN" \ - owner=LedgerHQ \ - repo=ledger-live-desktop \ - tag="$GH_TAG" \ - filename="dist/latest-linux.yml" +if [[ $(uname) == 'Linux' ]]; then + # -------------------------------------------------------------------- + # Linux: Internal process error (null) + # + # context: https://github.com/LedgerHQ/ledger-live-desktop/issues/1010 + # Linux: Internal process error (null) + # + # The "fix" is not optimal, as it doesn't really solve the problem + # (electron loading system openssl before we can load our embedded one) + # Quick summary: + # + # - build without publishing + # - unpack the .AppImage + # - download reported working libs from ubuntu mirrors, put it inside + # - re-pack the .AppImage + # - checksum stuff + # - upload to gh + + runJob \ + "DEBUG=electron-builder electron-builder build --publish never" \ + "building and packaging app..." \ + "app built and packaged successfully" \ + "failed to build app" \ + "verbose" + + runJob \ + "scripts/patch-appimage.sh" \ + "patching AppImage..." \ + "AppImage patched successfully" \ + "failed to patch AppImage" + + LEDGER_LIVE_VERSION=$(grep version package.json | sed -E 's/.*: "(.*)",/\1/g') + + scripts/upload-github-release-asset.sh \ + github_api_token="$GH_TOKEN" \ + owner=LedgerHQ \ + repo=ledger-live-desktop \ + tag="$GH_TAG" \ + filename="dist/ledger-live-desktop-$LEDGER_LIVE_VERSION-linux-x86_64.AppImage" + + scripts/upload-github-release-asset.sh \ + github_api_token="$GH_TOKEN" \ + owner=LedgerHQ \ + repo=ledger-live-desktop \ + tag="$GH_TAG" \ + filename="dist/latest-linux.yml" + +else + runJob \ + "DEBUG=electron-builder electron-builder build --publish always" \ + "building and packaging app..." \ + "app built and packaged successfully" \ + "failed to build app" \ + "verbose" +fi diff --git a/src/bridge/EthereumJSBridge.js b/src/bridge/EthereumJSBridge.js index 9713910c..0fd3f2a9 100644 --- a/src/bridge/EthereumJSBridge.js +++ b/src/bridge/EthereumJSBridge.js @@ -12,6 +12,7 @@ import { getDerivationModesForCurrency, getDerivationScheme, runDerivationScheme, + isIterableDerivationMode, getMandatoryEmptyAccountSkip, } from '@ledgerhq/live-common/lib/derivation' import { @@ -72,7 +73,7 @@ const txToOps = (account: Account) => (tx: Tx): Operation[] => { const value = BigNumber(tx.value) const fee = BigNumber(tx.gas_price * tx.gas_used) if (sending) { - ops.push({ + const op: $Exact = { id: `${account.id}-${tx.hash}-OUT`, hash: tx.hash, type: 'OUT', @@ -84,10 +85,12 @@ const txToOps = (account: Account) => (tx: Tx): Operation[] => { senders: [tx.from], recipients: [tx.to], date: new Date(tx.received_at), - }) + extra: {}, + } + ops.push(op) } if (receiving) { - ops.push({ + const op: $Exact = { id: `${account.id}-${tx.hash}-IN`, hash: tx.hash, type: 'IN', @@ -99,7 +102,9 @@ const txToOps = (account: Account) => (tx: Tx): Operation[] => { senders: [tx.from], recipients: [tx.to], date: new Date(new Date(tx.received_at) + 1), // hack: make the IN appear after the OUT in history. - }) + extra: {}, + } + ops.push(op) } return ops } @@ -168,7 +173,7 @@ const signAndBroadcast = async ({ const hash = await api.broadcastTransaction(transaction) - onOperationBroadcasted({ + const op: $Exact = { id: `${a.id}-${hash}-OUT`, hash, type: 'OUT', @@ -181,7 +186,10 @@ const signAndBroadcast = async ({ recipients: [t.recipient], transactionSequenceNumber: nonce, date: new Date(), - }) + extra: {}, + } + + onOperationBroadcasted(op) } } @@ -304,7 +312,8 @@ const EthereumBridge: WalletBridge = { let emptyCount = 0 const mandatoryEmptyAccountSkip = getMandatoryEmptyAccountSkip(derivationMode) const derivationScheme = getDerivationScheme({ derivationMode, currency }) - for (let index = 0; index < 255; index++) { + const stopAt = isIterableDerivationMode(derivationMode) ? 255 : 1 + for (let index = 0; index < stopAt; index++) { const freshAddressPath = runDerivationScheme(derivationScheme, currency, { account: index, }) diff --git a/src/bridge/LibcoreBridge.js b/src/bridge/LibcoreBridge.js index 4ad2bfc0..8ed8de3d 100644 --- a/src/bridge/LibcoreBridge.js +++ b/src/bridge/LibcoreBridge.js @@ -220,6 +220,7 @@ const LibcoreBridge: WalletBridge = { .send({ accountId: account.id, currencyId: account.currency.id, + blockHeight: account.blockHeight, xpub: account.xpub || '', // FIXME only reason is to build the op id. we need to consider another id for making op id. derivationMode: account.derivationMode, seedIdentifier: account.seedIdentifier, diff --git a/src/bridge/RippleJSBridge.js b/src/bridge/RippleJSBridge.js index 31744b63..27fe883f 100644 --- a/src/bridge/RippleJSBridge.js +++ b/src/bridge/RippleJSBridge.js @@ -11,6 +11,7 @@ import { getDerivationModesForCurrency, getDerivationScheme, runDerivationScheme, + isIterableDerivationMode, } from '@ledgerhq/live-common/lib/derivation' import { getAccountPlaceholderName, @@ -104,7 +105,7 @@ async function signAndBroadcast({ a, t, deviceId, isCancelled, onSigned, onOpera const hash = computeBinaryTransactionHash(transaction) - onOperationBroadcasted({ + const op: $Exact = { id: `${a.id}-${hash}-OUT`, hash, accountId: a.id, @@ -120,7 +121,9 @@ async function signAndBroadcast({ a, t, deviceId, isCancelled, onSigned, onOpera transactionSequenceNumber: (a.operations.length > 0 ? a.operations[0].transactionSequenceNumber : 0) + a.pendingOperations.length, - }) + extra: {}, + } + onOperationBroadcasted(op) } } finally { api.disconnect() @@ -230,6 +233,7 @@ const txToOperation = (account: Account) => ({ recipients: [destination.address], date: new Date(timestamp), transactionSequenceNumber: sequence, + extra: {}, } return op } @@ -299,7 +303,8 @@ const RippleJSBridge: WalletBridge = { const derivationModes = getDerivationModesForCurrency(currency) for (const derivationMode of derivationModes) { const derivationScheme = getDerivationScheme({ derivationMode, currency }) - for (let index = 0; index < 255; index++) { + const stopAt = isIterableDerivationMode(derivationMode) ? 255 : 1 + for (let index = 0; index < stopAt; index++) { const freshAddressPath = runDerivationScheme(derivationScheme, currency, { account: index, }) diff --git a/src/commands/libcoreGetFees.js b/src/commands/libcoreGetFees.js index ef66bcb2..c3eb9f7d 100644 --- a/src/commands/libcoreGetFees.js +++ b/src/commands/libcoreGetFees.js @@ -6,7 +6,7 @@ import withLibcore from 'helpers/withLibcore' import { createCommand, Command } from 'helpers/ipc' import { getCryptoCurrencyById } from '@ledgerhq/live-common/lib/currencies' import { getWalletName } from '@ledgerhq/live-common/lib/account' -import type { Account } from '@ledgerhq/live-common/lib/types' +import type { Account, DerivationMode } from '@ledgerhq/live-common/lib/types' import { isValidAddress, libcoreAmountToBigNumber, @@ -26,7 +26,7 @@ type Input = { accountIndex: number, transaction: BitcoinLikeTransaction, currencyId: string, - derivationMode: string, + derivationMode: DerivationMode, seedIdentifier: string, } diff --git a/src/commands/libcoreScanFromXPUB.js b/src/commands/libcoreScanFromXPUB.js index 0444f501..998e5c98 100644 --- a/src/commands/libcoreScanFromXPUB.js +++ b/src/commands/libcoreScanFromXPUB.js @@ -1,7 +1,7 @@ // @flow import { fromPromise } from 'rxjs/observable/fromPromise' -import type { AccountRaw } from '@ledgerhq/live-common/lib/types' +import type { AccountRaw, DerivationMode } from '@ledgerhq/live-common/lib/types' import { createCommand, Command } from 'helpers/ipc' import withLibcore from 'helpers/withLibcore' @@ -10,7 +10,7 @@ import { scanAccountsFromXPUB } from 'helpers/libcore' type Input = { currencyId: string, xpub: string, - derivationMode: string, + derivationMode: DerivationMode, seedIdentifier: string, } diff --git a/src/commands/libcoreSignAndBroadcast.js b/src/commands/libcoreSignAndBroadcast.js index 98cd8ff5..79cdaed5 100644 --- a/src/commands/libcoreSignAndBroadcast.js +++ b/src/commands/libcoreSignAndBroadcast.js @@ -2,17 +2,19 @@ import logger from 'logger' import { BigNumber } from 'bignumber.js' +import { StatusCodes } from '@ledgerhq/hw-transport' import Btc from '@ledgerhq/hw-app-btc' import { Observable } from 'rxjs' import { isSegwitDerivationMode } from '@ledgerhq/live-common/lib/derivation' import { getCryptoCurrencyById } from '@ledgerhq/live-common/lib/currencies' -import type { OperationRaw, CryptoCurrency } from '@ledgerhq/live-common/lib/types' +import type { OperationRaw, DerivationMode, CryptoCurrency } from '@ledgerhq/live-common/lib/types' import { getWalletName } from '@ledgerhq/live-common/lib/account' import { libcoreAmountToBigNumber, bigNumberToLibcoreAmount, getOrCreateWallet, } from 'helpers/libcore' +import { UpdateYourApp } from 'config/errors' import withLibcore from 'helpers/withLibcore' import { createCommand, Command } from 'helpers/ipc' @@ -26,8 +28,9 @@ type BitcoinLikeTransaction = { type Input = { accountId: string, + blockHeight: number, currencyId: string, - derivationMode: string, + derivationMode: DerivationMode, seedIdentifier: string, xpub: string, index: number, @@ -41,7 +44,17 @@ type Result = { type: 'signed' } | { type: 'broadcasted', operation: OperationRa const cmd: Command = createCommand( 'libcoreSignAndBroadcast', - ({ accountId, currencyId, derivationMode, seedIdentifier, xpub, index, transaction, deviceId }) => + ({ + accountId, + blockHeight, + currencyId, + derivationMode, + seedIdentifier, + xpub, + index, + transaction, + deviceId, + }) => Observable.create(o => { let unsubscribed = false const currency = getCryptoCurrencyById(currencyId) @@ -50,6 +63,7 @@ const cmd: Command = createCommand( doSignAndBroadcast({ accountId, currency, + blockHeight, derivationMode, seedIdentifier, xpub, @@ -79,6 +93,7 @@ const cmd: Command = createCommand( async function signTransaction({ hwApp, currency, + blockHeight, transaction, derivationMode, sigHashType, @@ -86,15 +101,21 @@ async function signTransaction({ }: { hwApp: Btc, currency: CryptoCurrency, + blockHeight: number, transaction: *, - derivationMode: string, + derivationMode: DerivationMode, sigHashType: number, hasTimestamp: boolean, }) { const additionals = [] let expiryHeight if (currency.id === 'bitcoin_cash' || currency.id === 'bitcoin_gold') additionals.push('bip143') - if (currency.id === 'zcash') expiryHeight = Buffer.from([0x00, 0x00, 0x00, 0x00]) + if (currency.id === 'zcash') { + expiryHeight = Buffer.from([0x00, 0x00, 0x00, 0x00]) + if (blockHeight >= 419200) { + additionals.push('sapling') + } + } const rawInputs = transaction.getInputs() const hasExtraData = currency.id === 'zcash' @@ -166,6 +187,7 @@ async function signTransaction({ export async function doSignAndBroadcast({ accountId, derivationMode, + blockHeight, seedIdentifier, currency, xpub, @@ -178,8 +200,9 @@ export async function doSignAndBroadcast({ onOperationBroadcasted, }: { accountId: string, - derivationMode: string, + derivationMode: DerivationMode, seedIdentifier: string, + blockHeight: number, currency: CryptoCurrency, xpub: string, index: number, @@ -222,12 +245,18 @@ export async function doSignAndBroadcast({ signTransaction({ hwApp: new Btc(transport), currency, + blockHeight, transaction: builded, sigHashType: parseInt(sigHashType, 16), hasTimestamp, derivationMode, }), - ) + ).catch(e => { + if (e && e.statusCode === StatusCodes.INCORRECT_P1_P2) { + throw new UpdateYourApp(`UpdateYourApp ${currency.id}`, currency) + } + throw e + }) if (!signedTransaction || isCancelled() || !njsAccount) return onSigned() @@ -251,7 +280,7 @@ export async function doSignAndBroadcast({ const fee = libcoreAmountToBigNumber(builded.getFees()) // NB we don't check isCancelled() because the broadcast is not cancellable now! - onOperationBroadcasted({ + const op: $Exact = { id: `${xpub}-${txHash}-OUT`, hash: txHash, type: 'OUT', @@ -265,7 +294,9 @@ export async function doSignAndBroadcast({ recipients, accountId, date: new Date().toISOString(), - }) + extra: {}, + } + onOperationBroadcasted(op) } export default cmd diff --git a/src/commands/libcoreSyncAccount.js b/src/commands/libcoreSyncAccount.js index 802428fa..e274ce62 100644 --- a/src/commands/libcoreSyncAccount.js +++ b/src/commands/libcoreSyncAccount.js @@ -1,6 +1,6 @@ // @flow -import type { AccountRaw } from '@ledgerhq/live-common/lib/types' +import type { AccountRaw, DerivationMode } from '@ledgerhq/live-common/lib/types' import { getCryptoCurrencyById } from '@ledgerhq/live-common/lib/currencies' import { fromPromise } from 'rxjs/observable/fromPromise' @@ -12,7 +12,7 @@ type Input = { accountId: string, currencyId: string, xpub: string, - derivationMode: string, + derivationMode: DerivationMode, seedIdentifier: string, index: number, } diff --git a/src/components/DevToolsPage/AccountImporter.js b/src/components/DevToolsPage/AccountImporter.js index 9bccb67f..17a525c1 100644 --- a/src/components/DevToolsPage/AccountImporter.js +++ b/src/components/DevToolsPage/AccountImporter.js @@ -1,15 +1,18 @@ // @flow +/* eslint-disable react/no-multi-comp */ + 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 type { Currency, Account, DerivationMode } from '@ledgerhq/live-common/lib/types' import { decodeAccount } from 'reducers/accounts' import { addAccount } from 'actions/accounts' -import FormattedVal from 'components/base/FormattedVal' +import FakeLink from 'components/base/FakeLink' +import Ellipsis from 'components/base/Ellipsis' import Switch from 'components/base/Switch' import Spinner from 'components/base/Spinner' import Box, { Card } from 'components/base/Box' @@ -32,26 +35,40 @@ type Props = { addAccount: Account => void, } -const INITIAL_STATE = { - status: 'idle', - currency: null, - xpub: '', - account: null, - isSegwit: true, - isUnsplit: false, - error: null, +type ImportableAccountType = { + name: string, + currency: Currency, + derivationMode: DerivationMode, + xpub: string, } type State = { status: string, + + importableAccounts: ImportableAccountType[], + currency: ?Currency, xpub: string, - account: ?Account, + name: string, isSegwit: boolean, isUnsplit: boolean, + error: ?Error, } +const INITIAL_STATE = { + status: 'idle', + + currency: null, + xpub: '', + name: 'dev', + isSegwit: true, + isUnsplit: false, + + error: null, + importableAccounts: [], +} + class AccountImporter extends PureComponent { state = INITIAL_STATE @@ -67,142 +84,199 @@ class AccountImporter extends PureComponent { onChangeXPUB = xpub => this.setState({ xpub }) onChangeSegwit = isSegwit => this.setState({ isSegwit }) onChangeUnsplit = isUnsplit => this.setState({ isUnsplit }) + onChangeName = name => this.setState({ name }) isValid = () => { - const { currency, xpub } = this.state - return !!currency && !!xpub + const { currency, xpub, status } = this.state + return !!currency && !!xpub && status !== 'scanning' } scan = async () => { - if (!this.isValid()) return this.setState({ status: 'scanning' }) + const { importableAccounts } = this.state try { - const { currency, xpub, isSegwit, isUnsplit } = this.state - invariant(currency, 'no currency') - const derivationMode = isSegwit - ? isUnsplit - ? 'segwit_unsplit' - : 'segwit' - : isUnsplit - ? 'unsplit' - : '' - const rawAccount = await scanFromXPUB - .send({ - seedIdentifier: 'dev_tool', - currencyId: currency.id, - xpub, - derivationMode, + for (let i = 0; i < importableAccounts.length; i++) { + const a = importableAccounts[i] + const scanPayload = { + seedIdentifier: `dev_${a.xpub}`, + currencyId: a.currency.id, + xpub: a.xpub, + derivationMode: a.derivationMode, + } + const rawAccount = await scanFromXPUB.send(scanPayload).toPromise() + const account = decodeAccount(rawAccount) + await this.import({ + ...account, + name: a.name, }) - .toPromise() - const account = decodeAccount(rawAccount) - this.setState({ status: 'finish', account }) + this.removeImportableAccount(a) + } + this.reset() } catch (error) { this.setState({ status: 'error', error }) } } - import = async () => { - const { account } = this.state + addToScan = () => { + const { xpub, currency, isSegwit, isUnsplit, name } = this.state + const derivationMode = isSegwit + ? isUnsplit + ? 'segwit_unsplit' + : 'segwit' + : isUnsplit + ? 'unsplit' + : '' + const importableAccount = { xpub, currency, derivationMode, name } + this.setState(({ importableAccounts }) => ({ + importableAccounts: [...importableAccounts, importableAccount], + currency: null, + xpub: '', + name: 'dev', + isSegwit: true, + isUnsplit: false, + })) + } + + removeImportableAccount = importableAccount => { + this.setState(({ importableAccounts }) => ({ + importableAccounts: importableAccounts.filter(i => i.xpub !== importableAccount.xpub), + })) + } + + import = async account => { 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 { + currency, + xpub, + name, + isSegwit, + isUnsplit, + status, + error, + importableAccounts, + } = this.state const supportsSplit = !!currency && !!currency.forkedFrom return ( - - {status === 'idle' ? ( - - - - - - {currency && (currency.supportsSegwit || supportsSplit) ? ( - - {supportsSplit && ( - - - {'unsplit'} + + + {status === 'idle' || status === 'scanning' ? ( + + + + + + {currency && (currency.supportsSegwit || supportsSplit) ? ( + + {supportsSplit && ( + + + {'unsplit'} + + - - - )} - {currency.supportsSegwit && ( - - - {'segwit'} + )} + {currency.supportsSegwit && ( + + + {'segwit'} + + - - - )} - - ) : null} - - - - - - - - - ) : status === 'scanning' ? ( - - - - ) : status === 'finish' ? ( - account ? ( - - - {currency && } - - {account.name} - - {`${account.operations.length} operation(s)`} + )} + ) : null} + + + - - - - ) : ( + + + + + + + + + ) : status === 'error' ? ( - {'No accounts found or wrong xpub'} + + + - ) - ) : status === 'error' ? ( - - - - - - - ) : null} - + ) : null} + + {!!importableAccounts.length && ( + + {importableAccounts.map((acc, i) => ( + + {acc.xpub} + + ))} + {status !== 'scanning' && ( + + + + )} + + )} + + ) + } +} + +class ImportableAccount extends PureComponent<{ + importableAccount: ImportableAccountType, + onRemove: ImportableAccountType => void, + isLoading: boolean, +}> { + remove = () => { + this.props.onRemove(this.props.importableAccount) + } + render() { + const { importableAccount, isLoading } = this.props + return ( + + {isLoading && } + + + {`[${importableAccount.name}] ${importableAccount.derivationMode || + 'default'} ${importableAccount.xpub}`} + + {!isLoading && ( + + {'Remove'} + + )} + ) } } diff --git a/src/components/modals/AddAccounts/steps/03-step-import.js b/src/components/modals/AddAccounts/steps/03-step-import.js index 4d2ef811..227e5f00 100644 --- a/src/components/modals/AddAccounts/steps/03-step-import.js +++ b/src/components/modals/AddAccounts/steps/03-step-import.js @@ -144,7 +144,9 @@ class StepImport extends PureComponent { }) } }, - complete: () => setScanStatus('finished'), + complete: () => { + setScanStatus('finished') + }, error: err => { logger.critical(err) setScanStatus('error', err) diff --git a/src/config/urls.js b/src/config/urls.js index 60c2fc42..c0ddc86b 100644 --- a/src/config/urls.js +++ b/src/config/urls.js @@ -8,7 +8,7 @@ export const urls = { // Ledger support faq: 'https://support.ledgerwallet.com/hc/en-us', - terms: 'https://www.ledgerwallet.com/terms', + terms: 'https://www.ledger.com/pages/terms-of-use-and-disclaimer', noDeviceBuyNew: 'https://www.ledgerwallet.com/', noDeviceTrackOrder: 'http://order.ledgerwallet.com/', noDeviceLearnMore: 'https://www.ledgerwallet.com/', diff --git a/src/helpers/countervalues.js b/src/helpers/countervalues.js index fd5fc37e..7ec06d56 100644 --- a/src/helpers/countervalues.js +++ b/src/helpers/countervalues.js @@ -14,6 +14,7 @@ import { import logger from 'logger' import { listCryptoCurrencies } from '@ledgerhq/live-common/lib/currencies' import type { CryptoCurrency } from '@ledgerhq/live-common/lib/types' +import network from '../api/network' const pairsSelector = createSelector( currenciesSelector, @@ -62,6 +63,7 @@ const CounterValues = createCounterValues({ pairsSelector, setExchangePairsAction, addExtraPollingHooks, + network, }) let sortCache diff --git a/src/helpers/libcore.js b/src/helpers/libcore.js index 93467443..b12709bd 100644 --- a/src/helpers/libcore.js +++ b/src/helpers/libcore.js @@ -11,6 +11,7 @@ import { getDerivationScheme, isSegwitDerivationMode, isUnsplitDerivationMode, + isIterableDerivationMode, } from '@ledgerhq/live-common/lib/derivation' import { getCryptoCurrencyById } from '@ledgerhq/live-common/lib/currencies' import { @@ -26,6 +27,7 @@ import type { AccountRaw, OperationRaw, OperationType, + DerivationMode, } from '@ledgerhq/live-common/lib/types' import type { NJSAccount, NJSOperation } from '@ledgerhq/ledger-core/src/ledgercore_doc' @@ -89,7 +91,7 @@ async function scanAccountsOnDeviceBySegwit({ currency: CryptoCurrency, onAccountScanned: AccountRaw => void, isUnsubscribed: () => boolean, - derivationMode: string, + derivationMode: DerivationMode, showNewAccount: boolean, }): Promise { const isSegwit = isSegwitDerivationMode(derivationMode) @@ -111,7 +113,6 @@ async function scanAccountsOnDeviceBySegwit({ // retrieve or create the wallet const wallet = await getOrCreateWallet(core, walletName, { currency, derivationMode }) - const accountsCount = await wallet.getAccountCount() // recursively scan all accounts on device on the given app // new accounts will be created in sqlite, existing ones will be updated @@ -121,7 +122,6 @@ async function scanAccountsOnDeviceBySegwit({ walletName, devicePath, currency, - accountsCount, accountIndex: 0, accounts: [], onAccountScanned, @@ -201,8 +201,7 @@ async function scanNextAccount(props: { devicePath: string, currency: CryptoCurrency, seedIdentifier: string, - derivationMode: string, - accountsCount: number, + derivationMode: DerivationMode, accountIndex: number, accounts: AccountRaw[], onAccountScanned: AccountRaw => void, @@ -215,7 +214,6 @@ async function scanNextAccount(props: { walletName, devicePath, currency, - accountsCount, accountIndex, accounts, onAccountScanned, @@ -225,13 +223,12 @@ async function scanNextAccount(props: { isUnsubscribed, } = props - // create account only if account has not been scanned yet - // if it has already been created, we just need to get it, and sync it - const hasBeenScanned = accountIndex < accountsCount - - const njsAccount = hasBeenScanned - ? await wallet.getAccount(accountIndex) - : await createAccount(wallet, devicePath) + let njsAccount + try { + njsAccount = await wallet.getAccount(accountIndex) + } catch (err) { + njsAccount = await createAccount(wallet, devicePath) + } if (isUnsubscribed()) return [] @@ -259,15 +256,15 @@ async function scanNextAccount(props: { if (isUnsubscribed()) return [] - const isEmpty = ops.length === 0 + const isLast = ops.length === 0 || !isIterableDerivationMode(derivationMode) - if (!isEmpty || showNewAccount) { + if (!isLast || showNewAccount) { onAccountScanned(account) accounts.push(account) } // returns if the current index points on an account with no ops - if (isEmpty) { + if (isLast) { return accounts } @@ -292,7 +289,7 @@ export async function getOrCreateWallet( derivationMode, }: { currency: CryptoCurrency, - derivationMode: string, + derivationMode: DerivationMode, }, ): NJSWallet { const pool = core.getPoolInstance() @@ -335,7 +332,7 @@ async function buildAccountRaw({ seedIdentifier: string, walletName: string, currency: CryptoCurrency, - derivationMode: string, + derivationMode: DerivationMode, accountIndex: number, core: *, ops: NJSOperation[], @@ -428,7 +425,7 @@ function buildOperationRaw({ core: *, op: NJSOperation, xpub: string, -}): OperationRaw { +}): $Exact { const bitcoinLikeOperation = op.asBitcoinLikeOperation() const bitcoinLikeTransaction = bitcoinLikeOperation.getTransaction() const hash = bitcoinLikeTransaction.getHash() @@ -462,6 +459,7 @@ function buildOperationRaw({ blockHash: null, accountId: xpub, // FIXME accountId: xpub !? date: op.getDate().toISOString(), + extra: {}, } } @@ -475,7 +473,7 @@ export async function syncAccount({ }: { core: *, xpub: string, - derivationMode: string, + derivationMode: DerivationMode, seedIdentifier: string, currency: CryptoCurrency, index: number, @@ -551,20 +549,18 @@ export async function scanAccountsFromXPUB({ core: *, currencyId: string, xpub: string, - derivationMode: string, + derivationMode: DerivationMode, seedIdentifier: string, }) { const currency = getCryptoCurrencyById(currencyId) const walletName = getWalletName({ currency, - seedIdentifier: 'debug', + seedIdentifier, derivationMode, }) const wallet = await getOrCreateWallet(core, walletName, { currency, derivationMode }) - await wallet.eraseDataSince(new Date(0)) - const index = 0 const isSegwit = isSegwitDerivationMode(derivationMode) diff --git a/src/internals/index.js b/src/internals/index.js index baf58aa6..a35995b8 100644 --- a/src/internals/index.js +++ b/src/internals/index.js @@ -1,4 +1,5 @@ // @flow +import '@babel/polyfill' import commands from 'commands' import logger from 'logger' import uuid from 'uuid/v4' diff --git a/static/i18n/en/app.json b/static/i18n/en/app.json index 61691b0c..f6ddfe72 100644 --- a/static/i18n/en/app.json +++ b/static/i18n/en/app.json @@ -170,7 +170,7 @@ "luno": "Luno makes it safe and easy to buy, store and learn about cryptocurrencies like Bitcoin and Ethereum", "shapeshift": "ShapeShift is an online marketplace where users can buy and sell digital assets. It is a fast and secure way for the world to buy and sell digital assets, with no lengthy signup process, no counterparty risk, and no friction.", "genesis": "Genesis is an institutional trading firm offering liquidity and borrow for digital currencies, including bitcoin, bitcoin cash, ethereum, ethereum classic, litecoin, and XRP.", - "kyber": "KYBER, his a trading platform for exchange and conversion of ERC-20 tokens" + "kyber": "Kyber is a trading platform for exchange and conversion of ERC-20 tokens" }, "genuinecheck": { "modal": { @@ -826,8 +826,8 @@ "description": "Please try again or contact Ledger Support" }, "UpdateYourApp": { - "title": "App update required. Uninstall and reinstall the {{managerAppName}} app in the Manager", - "description": null + "title": "App update required", + "description": "Uninstall and reinstall the {{managerAppName}} app in the Manager" }, "WebsocketConnectionError": { "title": "Sorry, try again (websocket error).", diff --git a/yarn.lock b/yarn.lock index 7278d3f1..b6e09d1b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1670,10 +1670,10 @@ camelcase "^5.0.0" prettier "^1.13.7" -"@ledgerhq/hw-app-btc@4.24.0": - version "4.24.0" - resolved "https://registry.yarnpkg.com/@ledgerhq/hw-app-btc/-/hw-app-btc-4.24.0.tgz#8889b2bc9b9583209ed24f832c96ea8d23e1dc74" - integrity sha512-OEc8UCcdAWp10PPM9Keoh8imuusmNVe2o/89ujMT5UIWOGCu7duezpsnCY11jGNxf2hyos6lezUMlUAOHBuISQ== +"@ledgerhq/hw-app-btc@^4.24.0", "@ledgerhq/hw-app-btc@^4.27.0": + version "4.27.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-app-btc/-/hw-app-btc-4.27.0.tgz#11fc822bd34a47a39b1a7ae03ced69cf1d432796" + integrity sha512-7Ck48wCBb6nd9UXarNeGOsOqbOTi2cs4AxFhbDNrVLvPiBSH0yEiNQEF95J6u5BxKkAdM1GV9LoRumR4KhZGqQ== dependencies: "@ledgerhq/hw-transport" "^4.24.0" create-hash "^1.1.3" @@ -1701,6 +1701,14 @@ "@ledgerhq/hw-transport" "^4.24.0" bip32-path "0.4.2" +"@ledgerhq/hw-app-xrp@^4.25.0": + version "4.25.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-app-xrp/-/hw-app-xrp-4.25.0.tgz#d97d7e85290dd2d1ec99f85747a26145e7a7383e" + integrity sha512-kG9S8CxUFMG1hbLBiKtoPMquzlTigndDsxhoXB8oywAdbGsoCi2cufMHV2p5Bek6YlGfn5J5p49Hx5iNIf2y5Q== + dependencies: + "@ledgerhq/hw-transport" "^4.24.0" + bip32-path "0.4.2" + "@ledgerhq/hw-transport-node-hid@4.24.0": version "4.24.0" resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-node-hid/-/hw-transport-node-hid-4.24.0.tgz#8457969d66819e8f7f50d5dd96527ab26cd3787d" @@ -1733,10 +1741,10 @@ dependencies: events "^3.0.0" -"@ledgerhq/ledger-core@2.0.0-rc.8": - version "2.0.0-rc.8" - resolved "https://registry.yarnpkg.com/@ledgerhq/ledger-core/-/ledger-core-2.0.0-rc.8.tgz#618ff2ca091464c71890678d3912921ee46f98af" - integrity sha512-UYEwlw7+JfRCPw1z2kw9EGs88wsk5XBGxMPpNPF1cdpE7extEL63Gr+4h4ibU6kXEeEIpfI0lZ2l/6HwzMw6EQ== +"@ledgerhq/ledger-core@2.0.0-rc.11": + version "2.0.0-rc.11" + resolved "https://registry.yarnpkg.com/@ledgerhq/ledger-core/-/ledger-core-2.0.0-rc.11.tgz#5b314e222f487dfa8f525ba1ef008ae30b289339" + integrity sha512-HmtUd3WrVhJQtjNe6qO/hGrnzrE2YbdaTQnLhsQyD3qN1vUwHmanHjqOqVLFRI8a3KqVdMFqYMvn3N5c0hsLuQ== dependencies: "@ledgerhq/hw-app-btc" "^4.7.3" "@ledgerhq/hw-transport-node-hid" "^4.7.6" @@ -1745,19 +1753,23 @@ bindings "^1.3.0" nan "^2.6.2" -"@ledgerhq/live-common@4.0.0-beta.1": - version "4.0.0-beta.1" - resolved "https://registry.yarnpkg.com/@ledgerhq/live-common/-/live-common-4.0.0-beta.1.tgz#52ed90757761a08a5f5c9c40c1fed0cb2f1fb4ca" - integrity sha512-Ms06Za/EI8yP/4GOAziCMecqIssZbkS5tjSCgaqd1h0zHLzM6i6PvgjhJyvG5SrET6+uQVp4YM4qbo2tc6irjQ== +"@ledgerhq/live-common@4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/live-common/-/live-common-4.3.0.tgz#f983cb691642f1372d89e6a9320986141ab31045" + integrity sha512-086MHRLvkPB7qxpzBGm2W1bE3yMbUxwCF2JuEMiQzgTK4fNPYV9mz+qR+qhYYcUXuXZQ2fCnCBYDqUlfo1Wb3g== dependencies: - axios "^0.18.0" + "@ledgerhq/hw-app-btc" "^4.24.0" + "@ledgerhq/hw-app-eth" "^4.24.0" + "@ledgerhq/hw-app-xrp" "^4.24.0" + "@ledgerhq/hw-transport" "^4.24.0" bignumber.js "^7.2.1" + eip55 "^1.0.3" invariant "^2.2.2" lodash "^4.17.4" node-lzw "^0.3.1" numeral "^2.0.6" prando "^3.0.1" - react "^16.4.0" + react "*" react-i18next "^8.0.7" react-redux "^5.0.7" redux "^4.0.0" @@ -13342,15 +13354,15 @@ react-treebeard@^2.1.0: shallowequal "^0.2.2" velocity-react "^1.3.1" -react@^16.2.0, react@^16.4.0, react@^16.4.1: - version "16.4.1" - resolved "https://registry.yarnpkg.com/react/-/react-16.4.1.tgz#de51ba5764b5dbcd1f9079037b862bd26b82fe32" - integrity sha512-3GEs0giKp6E0Oh/Y9ZC60CmYgUPnp7voH9fbjWsvXtYFb4EWtgQub0ADSq0sJR0BbHc4FThLLtzlcFaFXIorwg== +react@*, react@^16.2.0, react@^16.6.1: + version "16.6.1" + resolved "https://registry.yarnpkg.com/react/-/react-16.6.1.tgz#ee2aef4f0a09e494594882029821049772f915fe" + integrity sha512-OtawJThYlvRgm9BXK+xTL7BIlDx8vv21j+fbQDjRRUyok6y7NyjlweGorielTahLZHYIdKUoK2Dp9ByVWuMqxw== dependencies: - fbjs "^0.8.16" loose-envify "^1.1.0" object-assign "^4.1.1" - prop-types "^15.6.0" + prop-types "^15.6.2" + scheduler "^0.11.0" reactcss@^1.2.0: version "1.2.3" @@ -14249,6 +14261,14 @@ sax@^1.2.4, sax@~1.2.1, sax@~1.2.4: resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== +scheduler@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.11.0.tgz#def1f1bfa6550cc57981a87106e65e8aea41a6b5" + integrity sha512-MAYbBfmiEHxF0W+c4CxMpEqMYK+rYF584VP/qMKSiHM6lTkBKKYOJaDiSILpJHla6hBOsVd6GucPL46o2Uq3sg== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + schema-utils@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.3.0.tgz#f5877222ce3e931edae039f17eb3716e7137f8cf"