Browse Source

Merge branch 'master' into auto-update

master
Thibaut 7 years ago
committed by GitHub
parent
commit
f3c97677de
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      package.json
  2. 5
      src/bridge/EthereumJSBridge.js
  3. 145
      src/bridge/LibcoreBridge.js
  4. 6
      src/bridge/RippleJSBridge.js
  5. 2
      src/bridge/types.js
  6. 2
      src/commands/installOsuFirmware.js
  7. 44
      src/commands/libcoreScanAccounts.js
  8. 94
      src/commands/libcoreSignAndBroadcast.js
  9. 9
      src/commands/listApps.js
  10. 2
      src/components/App.js
  11. 2
      src/components/FeesField/BitcoinKind.js
  12. 14
      src/components/ManagerPage/AppsList.js
  13. 74
      src/components/ManagerPage/index.js
  14. 4
      src/components/Onboarding/OnboardingBreadcrumb.js
  15. 5
      src/components/Onboarding/OnboardingFooter.js
  16. 14
      src/components/Onboarding/index.js
  17. 107
      src/components/Onboarding/steps/Analytics.js
  18. 72
      src/components/Onboarding/steps/GenuineCheck.js
  19. 15
      src/components/Onboarding/steps/Init.js
  20. 2
      src/components/RecipientAddress/index.js
  21. 2
      src/components/base/FlipTicker/index.js
  22. 6
      src/components/modals/Send/01-step-amount.js
  23. 13
      src/helpers/apps/listApps.js
  24. 1
      src/helpers/ipc.js
  25. 6
      src/helpers/libcore.js
  26. 16
      src/icons/Recover.js
  27. 48
      src/internals/accounts/index.js
  28. 71
      src/internals/accounts/signAndBroadcastTransaction/btc.js
  29. 7
      src/internals/accounts/sync.js
  30. 6
      src/internals/devices/index.js
  31. 3
      src/internals/manager/index.js
  32. 27
      src/reducers/onboarding.js
  33. 2
      src/reducers/settings.js
  34. 8
      static/i18n/en/onboarding.yml
  35. 12
      yarn.lock

6
package.json

@ -3,7 +3,7 @@
"productName": "Ledger Live", "productName": "Ledger Live",
"description": "Ledger Live - Desktop", "description": "Ledger Live - Desktop",
"repository": "https://github.com/LedgerHQ/ledger-live-desktop", "repository": "https://github.com/LedgerHQ/ledger-live-desktop",
"version": "0.1.0-alpha.3", "version": "0.1.0-alpha.4",
"author": "Ledger", "author": "Ledger",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
@ -41,8 +41,8 @@
"@ledgerhq/hw-app-xrp": "^4.12.0", "@ledgerhq/hw-app-xrp": "^4.12.0",
"@ledgerhq/hw-transport": "^4.12.0", "@ledgerhq/hw-transport": "^4.12.0",
"@ledgerhq/hw-transport-node-hid": "^4.12.0", "@ledgerhq/hw-transport-node-hid": "^4.12.0",
"@ledgerhq/ledger-core": "^1.2.1", "@ledgerhq/ledger-core": "^1.3.0",
"@ledgerhq/live-common": "^2.9.1", "@ledgerhq/live-common": "2.11.0",
"axios": "^0.18.0", "axios": "^0.18.0",
"babel-runtime": "^6.26.0", "babel-runtime": "^6.26.0",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",

5
src/bridge/EthereumJSBridge.js

@ -115,7 +115,6 @@ const EthereumBridge: WalletBridge<Transaction> = {
let { txs } = await api.getTransactions(address) let { txs } = await api.getTransactions(address)
if (finished) return { complete: true } if (finished) return { complete: true }
const path = freshAddressPath // FIXME
const freshAddress = address const freshAddress = address
if (txs.length === 0) { if (txs.length === 0) {
@ -127,7 +126,6 @@ const EthereumBridge: WalletBridge<Transaction> = {
const account: $Exact<Account> = { const account: $Exact<Account> = {
id: accountId, id: accountId,
xpub: '', xpub: '',
path, // FIXME we probably not want the address path in the account.path
freshAddress, freshAddress,
freshAddressPath, freshAddressPath,
name: 'New Account', name: 'New Account',
@ -153,7 +151,6 @@ const EthereumBridge: WalletBridge<Transaction> = {
const account: $Exact<Account> = { const account: $Exact<Account> = {
id: accountId, id: accountId,
xpub: '', xpub: '',
path, // FIXME we probably not want the address path in the account.path
freshAddress, freshAddress,
freshAddressPath, freshAddressPath,
name: address.slice(32), name: address.slice(32),
@ -308,7 +305,7 @@ const EthereumBridge: WalletBridge<Transaction> = {
.send({ .send({
currencyId: a.currency.id, currencyId: a.currency.id,
devicePath: deviceId, devicePath: deviceId,
path: a.path, path: a.freshAddressPath,
transaction: { ...t, nonce }, transaction: { ...t, nonce },
}) })
.toPromise() .toPromise()

145
src/bridge/LibcoreBridge.js

@ -1,21 +1,20 @@
// @flow // @flow
import React from 'react' import React from 'react'
import { ipcRenderer } from 'electron' import { map } from 'rxjs/operators'
import { decodeAccount, encodeAccount } from 'reducers/accounts' import { decodeAccount, encodeAccount } from 'reducers/accounts'
import runJob from 'renderer/runJob'
import FeesBitcoinKind from 'components/FeesField/BitcoinKind' import FeesBitcoinKind from 'components/FeesField/BitcoinKind'
import AdvancedOptionsBitcoinKind from 'components/AdvancedOptions/BitcoinKind' import libcoreScanAccounts from 'commands/libcoreScanAccounts'
import libcoreSignAndBroadcast from 'commands/libcoreSignAndBroadcast'
// import AdvancedOptionsBitcoinKind from 'components/AdvancedOptions/BitcoinKind'
import type { WalletBridge, EditProps } from './types' import type { WalletBridge, EditProps } from './types'
const notImplemented = new Error('LibcoreBridge: not implemented') const notImplemented = new Error('LibcoreBridge: not implemented')
// TODO for ipcRenderer listeners we should have a concept of requestId because type Transaction = {
// to be able to listen to events that only concerns you amount: number,
feePerByte: number,
// IMPORTANT: please read ./types.js that specify & document everything recipient: string,
}
type Transaction = *
const EditFees = ({ account, onChange, value }: EditProps<Transaction>) => ( const EditFees = ({ account, onChange, value }: EditProps<Transaction>) => (
<FeesBitcoinKind <FeesBitcoinKind
@ -27,6 +26,8 @@ const EditFees = ({ account, onChange, value }: EditProps<Transaction>) => (
/> />
) )
const EditAdvancedOptions = undefined // Not implemented yet
/*
const EditAdvancedOptions = ({ onChange, value }: EditProps<Transaction>) => ( const EditAdvancedOptions = ({ onChange, value }: EditProps<Transaction>) => (
<AdvancedOptionsBitcoinKind <AdvancedOptionsBitcoinKind
isRBF={value.isRBF} isRBF={value.isRBF}
@ -35,93 +36,32 @@ const EditAdvancedOptions = ({ onChange, value }: EditProps<Transaction>) => (
}} }}
/> />
) )
*/
const LibcoreBridge: WalletBridge<Transaction> = { const LibcoreBridge: WalletBridge<Transaction> = {
synchronize(initialAccount, { next, complete, error }) { scanAccountsOnDevice(currency, devicePath, observer) {
const unbind = () => ipcRenderer.removeListener('msg', handleAccountSync) return libcoreScanAccounts
.send({
function handleAccountSync(e, msg) { devicePath,
switch (msg.type) {
case 'account.sync.progress': {
next(a => a)
// FIXME TODO: use next(), to actually emit account updates.....
// - need to sync the balance
// - need to sync block height & block hash
// - need to sync operations.
// - once all that, need to set lastSyncDate to new Date()
// - when you implement addPendingOperation you also here need to:
// - if there were pendingOperations that are now in operations, remove them as well.
// - if there are pendingOperations that is older than a threshold (that depends on blockchain speed typically)
// then we probably should trash them out? it's a complex question for UI
break
}
case 'account.sync.fail': {
unbind()
error(new Error('failed')) // TODO more error detail
break
}
case 'account.sync.success': {
unbind()
complete()
break
}
default:
}
}
ipcRenderer.on('msg', handleAccountSync)
// TODO how to start the sync ?!
return {
unsubscribe() {
unbind()
console.warn('LibcoreBridge: interrupting synchronization is not supported')
},
}
},
scanAccountsOnDevice(currency, deviceId, { next, complete, error }) {
const unbind = () => ipcRenderer.removeListener('msg', handleMsgEvent)
function handleMsgEvent(e, { data, type }) {
if (type === 'accounts.scanAccountsOnDevice.accountScanned') {
next({ ...decodeAccount(data), archived: true })
}
}
ipcRenderer.on('msg', handleMsgEvent)
let unsubscribed
runJob({
channel: 'accounts',
job: 'scan',
successResponse: 'accounts.scanAccountsOnDevice.success',
errorResponse: 'accounts.scanAccountsOnDevice.fail',
data: {
devicePath: deviceId,
currencyId: currency.id, currencyId: currency.id,
}, })
}).then( .pipe(map(decodeAccount))
() => { .subscribe(observer)
if (unsubscribed) return },
unbind()
complete()
},
e => {
if (unsubscribed) return
unbind()
error(e)
},
)
synchronize(_initialAccount, _observer) {
// FIXME TODO: use next(), to actually emit account updates.....
// - need to sync the balance
// - need to sync block height & block hash
// - need to sync operations.
// - once all that, need to set lastSyncDate to new Date()
// - when you implement addPendingOperation you also here need to:
// - if there were pendingOperations that are now in operations, remove them as well.
// - if there are pendingOperations that is older than a threshold (that depends on blockchain speed typically)
// then we probably should trash them out? it's a complex question for UI
return { return {
unsubscribe() { unsubscribe() {
unsubscribed = true console.warn('LibcoreBridge: sync not implemented')
unbind()
console.warn('LibcoreBridge: interrupting scanAccounts is not implemented') // FIXME
}, },
} }
}, },
@ -165,15 +105,20 @@ const LibcoreBridge: WalletBridge<Transaction> = {
getMaxAmount: (a, _t) => Promise.resolve(a.balance), // FIXME getMaxAmount: (a, _t) => Promise.resolve(a.balance), // FIXME
signAndBroadcast: (account, transaction, deviceId) => { signAndBroadcast: async (account, transaction, deviceId) => {
const rawAccount = encodeAccount(account) const encodedAccount = encodeAccount(account)
return runJob({ const rawOp = await libcoreSignAndBroadcast
channel: 'accounts', .send({
job: 'signAndBroadcastTransactionBTCLike', account: encodedAccount,
successResponse: 'accounts.signAndBroadcastTransactionBTCLike.success', transaction,
errorResponse: 'accounts.signAndBroadcastTransactionBTCLike.fail', deviceId,
data: { account: rawAccount, transaction, deviceId }, })
}) .toPromise()
// quick HACK
const [op] = decodeAccount({ ...encodedAccount, operations: [rawOp] }).operations
return op
}, },
} }

6
src/bridge/RippleJSBridge.js

@ -188,8 +188,6 @@ const RippleJSBridge: WalletBridge<Transaction> = {
for (const derivation of derivations) { for (const derivation of derivations) {
for (let index = 0; index < 255; index++) { for (let index = 0; index < 255; index++) {
const freshAddressPath = derivation({ currency, x: index, segwit: false }) const freshAddressPath = derivation({ currency, x: index, segwit: false })
const path = freshAddressPath
// FIXME^ we need the account path, not the address path
const { address } = await await getAddress const { address } = await await getAddress
.send({ currencyId: currency.id, devicePath: deviceId, path: freshAddressPath }) .send({ currencyId: currency.id, devicePath: deviceId, path: freshAddressPath })
.toPromise() .toPromise()
@ -215,7 +213,6 @@ const RippleJSBridge: WalletBridge<Transaction> = {
next({ next({
id: accountId, id: accountId,
xpub: '', xpub: '',
path,
name: 'New Account', name: 'New Account',
freshAddress, freshAddress,
freshAddressPath, freshAddressPath,
@ -247,7 +244,6 @@ const RippleJSBridge: WalletBridge<Transaction> = {
const account: $Exact<Account> = { const account: $Exact<Account> = {
id: accountId, id: accountId,
xpub: '', xpub: '',
path,
name: address.slice(0, 8), name: address.slice(0, 8),
freshAddress, freshAddress,
freshAddressPath, freshAddressPath,
@ -426,7 +422,7 @@ const RippleJSBridge: WalletBridge<Transaction> = {
.send({ .send({
currencyId: a.currency.id, currencyId: a.currency.id,
devicePath: deviceId, devicePath: deviceId,
path: a.path, path: a.freshAddressPath,
transaction: JSON.parse(prepared.txJSON), transaction: JSON.parse(prepared.txJSON),
}) })
.toPromise() .toPromise()

2
src/bridge/types.js

@ -15,7 +15,7 @@ export type Observer<T> = {
} }
export type Subscription = { export type Subscription = {
unsubscribe: () => void, +unsubscribe: () => void,
} }
export type EditProps<Transaction> = { export type EditProps<Transaction> = {

2
src/commands/installOsuFirmware.js

@ -2,8 +2,8 @@
import { createCommand, Command } from 'helpers/ipc' import { createCommand, Command } from 'helpers/ipc'
import { fromPromise } from 'rxjs/observable/fromPromise' import { fromPromise } from 'rxjs/observable/fromPromise'
import { withDevice } from 'helpers/deviceAccess'
import { withDevice } from 'helpers/deviceAccess'
import installOsuFirmware from 'helpers/firmware/installOsuFirmware' import installOsuFirmware from 'helpers/firmware/installOsuFirmware'
type Input = { type Input = {

44
src/commands/libcoreScanAccounts.js

@ -0,0 +1,44 @@
// @flow
import type { AccountRaw } from '@ledgerhq/live-common/lib/types'
import { createCommand, Command } from 'helpers/ipc'
import { Observable } from 'rxjs'
import { scanAccountsOnDevice } from 'helpers/libcore'
type Input = {
devicePath: string,
currencyId: string,
}
type Result = AccountRaw
const cmd: Command<Input, Result> = createCommand(
'devices',
'libcoreScanAccounts',
({ devicePath, currencyId }) =>
Observable.create(o => {
// TODO scanAccountsOnDevice should directly return a Observable so we just have to pass-in
scanAccountsOnDevice({
devicePath,
currencyId,
onAccountScanned: account => {
o.next(account)
},
}).then(
() => {
o.complete()
},
e => {
o.error(e)
},
)
function unsubscribe() {
// FIXME not implemented
}
return unsubscribe
}),
)
export default cmd

94
src/commands/libcoreSignAndBroadcast.js

@ -0,0 +1,94 @@
// @flow
import type { AccountRaw, OperationRaw } from '@ledgerhq/live-common/lib/types'
import Btc from '@ledgerhq/hw-app-btc'
import { createCommand, Command } from 'helpers/ipc'
import { withDevice } from 'helpers/deviceAccess'
import { getWalletIdentifier } from 'helpers/libcore'
import { fromPromise } from 'rxjs/observable/fromPromise'
import { getCryptoCurrencyById } from '@ledgerhq/live-common/lib/helpers/currencies'
type BitcoinLikeTransaction = {
amount: number,
feePerByte: number,
recipient: string,
}
type Input = {
account: AccountRaw,
transaction: BitcoinLikeTransaction,
deviceId: string,
}
type Result = $Exact<OperationRaw>
const cmd: Command<Input, Result> = createCommand(
'devices',
'libcoreSignAndBroadcast',
({ account, transaction, deviceId }) => {
// TODO: investigate why importing it on file scope causes trouble
const core = require('init-ledger-core')()
return fromPromise(
withDevice(deviceId)(async transport => {
const hwApp = new Btc(transport)
const WALLET_IDENTIFIER = await getWalletIdentifier({
hwApp,
isSegwit: !!account.isSegwit,
currencyId: account.currencyId,
devicePath: deviceId,
})
const njsWallet = await core.getWallet(WALLET_IDENTIFIER)
const njsAccount = await njsWallet.getAccount(account.index)
const bitcoinLikeAccount = njsAccount.asBitcoinLikeAccount()
const njsWalletCurrency = njsWallet.getCurrency()
const amount = core.createAmount(njsWalletCurrency, transaction.amount)
const fees = core.createAmount(njsWalletCurrency, transaction.feePerByte)
const transactionBuilder = bitcoinLikeAccount.buildTransaction()
// TODO: check if is valid address. if not, it will fail silently on invalid
transactionBuilder.sendToAddress(amount, transaction.recipient)
// TODO: don't use hardcoded value for sequence (and first also maybe)
transactionBuilder.pickInputs(0, 0xffffff)
transactionBuilder.setFeesPerByte(fees)
const builded = await transactionBuilder.build()
const sigHashType = core.helpers.bytesToHex(
njsWalletCurrency.bitcoinLikeNetworkParameters.SigHash,
)
const currency = getCryptoCurrencyById(account.currencyId)
const signedTransaction = await core.signTransaction({
hwApp,
transaction: builded,
sigHashType,
supportsSegwit: currency.supportsSegwit,
isSegwit: account.isSegwit,
})
const txHash = await njsAccount
.asBitcoinLikeAccount()
.broadcastRawTransaction(signedTransaction)
// optimistic operation
return {
id: txHash,
hash: txHash,
type: 'OUT',
value: amount,
blockHash: null,
blockHeight: null,
senders: [account.freshAddress],
recipients: [transaction.recipient],
accountId: account.id,
date: new Date().toISOString(),
}
}),
)
},
)
export default cmd

9
src/commands/listApps.js

@ -5,11 +5,14 @@ import { fromPromise } from 'rxjs/observable/fromPromise'
import listApps from 'helpers/apps/listApps' import listApps from 'helpers/apps/listApps'
type Input = * type Input = {
targetId: string | number,
}
type Result = * type Result = *
const cmd: Command<Input, Result> = createCommand('manager', 'listApps', () => const cmd: Command<Input, Result> = createCommand('devices', 'listApps', ({ targetId }) =>
fromPromise(listApps()), fromPromise(listApps(targetId)),
) )
export default cmd export default cmd

2
src/components/App.js

@ -20,8 +20,6 @@ import Print from 'components/layout/Print'
import CounterValues from 'helpers/countervalues' import CounterValues from 'helpers/countervalues'
import { BridgeSyncProvider } from 'bridge/BridgeSyncContext' import { BridgeSyncProvider } from 'bridge/BridgeSyncContext'
const { DEV_TOOLS } = process.env
const App = ({ const App = ({
store, store,
history, history,

2
src/components/FeesField/BitcoinKind.js

@ -53,7 +53,7 @@ class FeesField extends Component<Props & { fees?: Fees, error?: Error }, { item
let items: FeeItem[] = [] let items: FeeItem[] = []
if (fees) { if (fees) {
for (const key of Object.keys(fees)) { for (const key of Object.keys(fees)) {
const feePerByte = Math.floor(fees[key] / 1000) const feePerByte = Math.ceil(fees[key] / 1000)
const blockCount = parseInt(key, 10) const blockCount = parseInt(key, 10)
if (!isNaN(blockCount) && !isNaN(feePerByte)) { if (!isNaN(blockCount) && !isNaN(feePerByte)) {
items.push({ key, blockCount, feePerByte }) items.push({ key, blockCount, feePerByte })

14
src/components/ManagerPage/AppsList.js

@ -42,6 +42,7 @@ type LedgerApp = {
type Props = { type Props = {
device: Device, device: Device,
targetId: string | number,
t: T, t: T,
} }
@ -69,10 +70,15 @@ class AppsList extends PureComponent<Props, State> {
_unmounted = false _unmounted = false
async fetchAppList() { async fetchAppList() {
const appsList = CACHED_APPS || (await listApps.send().toPromise()) try {
CACHED_APPS = appsList const { targetId } = this.props
if (!this._unmounted) { const appsList = CACHED_APPS || (await listApps.send({ targetId }).toPromise())
this.setState({ appsList, status: 'idle' }) CACHED_APPS = appsList
if (!this._unmounted) {
this.setState({ appsList, status: 'idle' })
}
} catch (err) {
this.setState({ status: 'error', error: err.message })
} }
} }

74
src/components/ManagerPage/index.js

@ -1,13 +1,14 @@
// @flow // @flow
import React, { Component, Fragment } from 'react' import React, { Fragment } from 'react'
import { translate } from 'react-i18next' import { translate } from 'react-i18next'
import type { Node } from 'react'
import type { T } from 'types/common' import type { T } from 'types/common'
import AppsList from './AppsList' import AppsList from './AppsList'
// import DeviceInfos from './DeviceInfos' // import DeviceInfos from './DeviceInfos'
import FirmwareUpdate from './FirmwareUpdate' // import FirmwareUpdate from './FirmwareUpdate'
import EnsureDevice from './EnsureDevice' import EnsureDevice from './EnsureDevice'
import EnsureDashboard from './EnsureDashboard' import EnsureDashboard from './EnsureDashboard'
import EnsureGenuine from './EnsureGenuine' import EnsureGenuine from './EnsureGenuine'
@ -16,44 +17,35 @@ type Props = {
t: T, t: T,
} }
type State = {} const ManagerPage = ({ t }: Props): Node => (
<Fragment>
class ManagerPage extends Component<Props, State> { <EnsureDevice>
render() { {device => (
const { t } = this.props <EnsureDashboard device={device}>
{deviceInfo => (
return ( <Fragment>
<Fragment> {deviceInfo.mcu && <span>bootloader mode</span>}
<EnsureDevice> {deviceInfo.final && <span>osu mode</span>}
{device => (
<EnsureDashboard device={device}> {!deviceInfo.mcu &&
{deviceInfo => ( !deviceInfo.final && (
<Fragment> <EnsureGenuine device={device} t={t}>
{deviceInfo.mcu && <span>bootloader mode</span>} {/* <FirmwareUpdate
{deviceInfo.final && <span>osu mode</span>} infos={{
targetId: deviceInfo.targetId,
{!deviceInfo.mcu && version: deviceInfo.version,
!deviceInfo.final && ( }}
<EnsureGenuine device={device} t={t}> device={device}
<FirmwareUpdate t={t}
infos={{ /> */}
targetId: deviceInfo.targetId, <AppsList device={device} targetId={deviceInfo.targetId} />
version: deviceInfo.version, </EnsureGenuine>
}} )}
device={device} </Fragment>
t={t}
/>
<AppsList device={device} />
</EnsureGenuine>
)}
</Fragment>
)}
</EnsureDashboard>
)} )}
</EnsureDevice> </EnsureDashboard>
</Fragment> )}
) </EnsureDevice>
} </Fragment>
} )
export default translate()(ManagerPage) export default translate()(ManagerPage)

4
src/components/Onboarding/OnboardingBreadcrumb.js

@ -18,7 +18,7 @@ type Props = {
function OnboardingBreadcrumb(props: Props) { function OnboardingBreadcrumb(props: Props) {
const { onboarding } = props const { onboarding } = props
const { stepName, isGenuineFail } = onboarding const { stepName, genuine } = onboarding
const filteredSteps = onboarding.steps const filteredSteps = onboarding.steps
.filter(step => !step.external) .filter(step => !step.external)
@ -29,7 +29,7 @@ function OnboardingBreadcrumb(props: Props) {
return ( return (
<Breadcrumb <Breadcrumb
stepsErrors={isGenuineFail ? [genuineStepIndex] : undefined} stepsErrors={genuine.isGenuineFail ? [genuineStepIndex] : undefined}
currentStep={stepIndex} currentStep={stepIndex}
items={filteredSteps} items={filteredSteps}
/> />

5
src/components/Onboarding/OnboardingFooter.js

@ -22,14 +22,15 @@ type Props = {
t: T, t: T,
nextStep: () => void, nextStep: () => void,
prevStep: () => void, prevStep: () => void,
isContinueDisabled?: boolean,
} }
const OnboardingFooter = ({ t, nextStep, prevStep, ...props }: Props) => ( const OnboardingFooter = ({ t, nextStep, prevStep, isContinueDisabled, ...props }: Props) => (
<Wrapper {...props}> <Wrapper {...props}>
<Button small outline onClick={() => prevStep()}> <Button small outline onClick={() => prevStep()}>
{t('common:back')} {t('common:back')}
</Button> </Button>
<Button small primary onClick={() => nextStep()} ml="auto"> <Button disabled={isContinueDisabled} small primary onClick={() => nextStep()} ml="auto">
{t('common:continue')} {t('common:continue')}
</Button> </Button>
</Wrapper> </Wrapper>

14
src/components/Onboarding/index.js

@ -10,13 +10,7 @@ import type { T } from 'types/common'
import type { OnboardingState } from 'reducers/onboarding' import type { OnboardingState } from 'reducers/onboarding'
import { saveSettings } from 'actions/settings' import { saveSettings } from 'actions/settings'
import { import { nextStep, prevStep, jumpStep, updateGenuineCheck, isLedgerNano } from 'reducers/onboarding'
nextStep,
prevStep,
jumpStep,
setGenuineCheckFail,
isLedgerNano,
} from 'reducers/onboarding'
import { getCurrentDevice } from 'reducers/devices' import { getCurrentDevice } from 'reducers/devices'
// import { unlock } from 'reducers/application' // import { unlock } from 'reducers/application'
@ -80,9 +74,10 @@ export type StepProps = {
nextStep: Function, nextStep: Function,
jumpStep: Function, jumpStep: Function,
finish: Function, finish: Function,
saveSettings: Function,
// savePassword: Function, // savePassword: Function,
getDeviceInfo: Function, getDeviceInfo: Function,
setGenuineCheckFail: Function, updateGenuineCheck: Function,
isLedgerNano: Function, isLedgerNano: Function,
} }
@ -116,7 +111,7 @@ class Onboarding extends PureComponent<Props> {
const stepProps: StepProps = { const stepProps: StepProps = {
t, t,
onboarding, onboarding,
setGenuineCheckFail, updateGenuineCheck,
isLedgerNano, isLedgerNano,
prevStep, prevStep,
nextStep, nextStep,
@ -124,6 +119,7 @@ class Onboarding extends PureComponent<Props> {
finish: this.finish, finish: this.finish,
// savePassword: this.savePassword, // savePassword: this.savePassword,
getDeviceInfo: this.getDeviceInfo, getDeviceInfo: this.getDeviceInfo,
saveSettings,
} }
return ( return (

107
src/components/Onboarding/steps/Analytics.js

@ -1,7 +1,9 @@
// @flow // @flow
import React from 'react' import React, { PureComponent } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { connect } from 'react-redux'
import { saveSettings } from 'actions/settings'
import Box from 'components/base/Box' import Box from 'components/base/Box'
import CheckBox from 'components/base/CheckBox' import CheckBox from 'components/base/CheckBox'
@ -10,51 +12,78 @@ import OnboardingFooter from '../OnboardingFooter'
import type { StepProps } from '..' import type { StepProps } from '..'
export default (props: StepProps) => { const mapDispatchToProps = { saveSettings }
const { nextStep, prevStep, t } = props
return (
<Box sticky pt={150}>
<Box grow alignItems="center">
<Title>{t('onboarding:analytics.title')}</Title>
<Description>{t('onboarding:analytics.desc')}</Description>
<Box mt={5}> type State = {
<Container> analyticsToggle: boolean,
<Box justify="center"> termsConditionsToggle: boolean,
<Box horizontal> }
<AnalyticsTitle>{t('onboarding:analytics.shareDiagnostics.title')}</AnalyticsTitle> class Analytics extends PureComponent<StepProps, State> {
state = {
analyticsToggle: false,
termsConditionsToggle: false,
}
handleAnalyticsToggle = (isChecked: boolean) => {
this.setState({ analyticsToggle: !this.state.analyticsToggle })
this.props.saveSettings({
shareAnalytics: isChecked,
})
}
handleTermsToggle = () => {
this.setState({ termsConditionsToggle: !this.state.termsConditionsToggle })
}
render() {
const { nextStep, prevStep, t } = this.props
const { analyticsToggle, termsConditionsToggle } = this.state
return (
<Box sticky pt={150}>
<Box grow alignItems="center">
<Title>{t('onboarding:analytics.title')}</Title>
<Description>{t('onboarding:analytics.desc')}</Description>
<Box mt={5}>
<Container>
<Box justify="center" style={{ width: 450 }}>
<Box horizontal>
<AnalyticsTitle>{t('onboarding:analytics.shareAnalytics.title')}</AnalyticsTitle>
</Box>
<AnalyticsText>{t('onboarding:analytics.shareAnalytics.desc')}</AnalyticsText>
</Box> </Box>
<AnalyticsText>{t('onboarding:analytics.shareDiagnostics.desc')}</AnalyticsText> <Box alignItems="center" horizontal mx={5}>
</Box> <CheckBox isChecked={analyticsToggle} onChange={this.handleAnalyticsToggle} />
<Box alignItems="center" horizontal mx={5}>
<CheckBox isChecked={false} />
</Box>
</Container>
<Container>
<Box justify="center">
<Box horizontal>
<AnalyticsTitle>{t('onboarding:analytics.shareDiagnostics.title')}</AnalyticsTitle>
</Box> </Box>
<AnalyticsText>{t('onboarding:analytics.shareDiagnostics.desc')}</AnalyticsText> </Container>
</Box> <Container>
<Box alignItems="center" horizontal mx={5}> <Box justify="center" style={{ width: 450 }}>
<CheckBox isChecked={false} /> <Box horizontal>
</Box> <AnalyticsTitle>{t('onboarding:analytics.termsConditions.title')}</AnalyticsTitle>
</Container> </Box>
<AnalyticsText>{t('onboarding:analytics.termsConditions.desc')}</AnalyticsText>
</Box>
<Box alignItems="center" horizontal mx={5}>
<CheckBox isChecked={termsConditionsToggle} onChange={this.handleTermsToggle} />
</Box>
</Container>
</Box>
</Box> </Box>
<OnboardingFooter
horizontal
align="center"
flow={2}
t={t}
nextStep={nextStep}
prevStep={prevStep}
isContinueDisabled={!termsConditionsToggle}
/>
</Box> </Box>
<OnboardingFooter )
horizontal }
align="center"
flow={2}
t={t}
nextStep={nextStep}
prevStep={prevStep}
/>
</Box>
)
} }
export default connect(null, mapDispatchToProps)(Analytics)
export const AnalyticsText = styled(Box).attrs({ export const AnalyticsText = styled(Box).attrs({
ff: 'Open Sans|Regular', ff: 'Open Sans|Regular',
fontSize: 3, fontSize: 3,

72
src/components/Onboarding/steps/GenuineCheck.js

@ -1,13 +1,14 @@
// @flow // @flow
import React, { PureComponent, Fragment } from 'react' import React, { PureComponent, Fragment } from 'react'
import { shell } from 'electron'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import styled from 'styled-components' import styled from 'styled-components'
import { radii } from 'styles/theme' import { radii } from 'styles/theme'
import type { T } from 'types/common' import type { T } from 'types/common'
import { setGenuineCheckFail } from 'reducers/onboarding' import { updateGenuineCheck } from 'reducers/onboarding'
import Box, { Card } from 'components/base/Box' import Box, { Card } from 'components/base/Box'
import Button from 'components/base/Button' import Button from 'components/base/Button'
@ -23,25 +24,25 @@ import { Title, Description, IconOptionRow } from '../helperComponents'
import type { StepProps } from '..' import type { StepProps } from '..'
import OnboardingFooter from '../OnboardingFooter' import OnboardingFooter from '../OnboardingFooter'
const mapDispatchToProps = { setGenuineCheckFail } const mapDispatchToProps = { updateGenuineCheck }
type State = { type State = {
pinStepPass: boolean | null,
phraseStepPass: boolean | null,
cachedPinStepButton: string, cachedPinStepButton: string,
cachedPhraseStepButton: string, cachedRecoveryStepButton: string,
isGenuineCheckModalOpened: boolean, isGenuineCheckModalOpened: boolean,
isDeviceGenuine: boolean, }
const INITIAL_STATE = {
cachedPinStepButton: '',
cachedRecoveryStepButton: '',
isGenuineCheckModalOpened: false,
} }
class GenuineCheck extends PureComponent<StepProps, State> { class GenuineCheck extends PureComponent<StepProps, State> {
state = { state = {
pinStepPass: null, ...INITIAL_STATE,
phraseStepPass: null, cachedPinStepButton: this.props.onboarding.genuine.pinStepPass ? 'yes' : '',
cachedPinStepButton: '', cachedRecoveryStepButton: this.props.onboarding.genuine.recoveryStepPass ? 'yes' : '',
cachedPhraseStepButton: '',
isGenuineCheckModalOpened: false,
isDeviceGenuine: false,
} }
getButtonLabel() { getButtonLabel() {
@ -61,15 +62,21 @@ class GenuineCheck extends PureComponent<StepProps, State> {
} }
handleButtonPass = (item: Object, step: string) => { handleButtonPass = (item: Object, step: string) => {
this.setState({ [`${step}`]: item.pass }) this.props.updateGenuineCheck({ [`${step}`]: item.pass })
if (step === 'pinStepPass') { if (step === 'pinStepPass') {
this.setState({ cachedPinStepButton: item.key }) this.setState({ cachedPinStepButton: item.key })
} else { } else {
this.setState({ cachedPhraseStepButton: item.key }) this.setState({ cachedRecoveryStepButton: item.key })
} }
if (!item.pass) { if (!item.pass) {
this.props.setGenuineCheckFail(true) this.setState(INITIAL_STATE)
this.props.updateGenuineCheck({
isGenuineFail: true,
recoveryStepPass: false,
pinStepPass: false,
isDeviceGenuine: false,
})
} }
} }
@ -79,15 +86,19 @@ class GenuineCheck extends PureComponent<StepProps, State> {
handleGenuineCheck = async isGenuine => { handleGenuineCheck = async isGenuine => {
await new Promise(r => setTimeout(r, 1e3)) // let's wait a bit before closing modal await new Promise(r => setTimeout(r, 1e3)) // let's wait a bit before closing modal
this.handleCloseGenuineCheckModal() this.handleCloseGenuineCheckModal()
this.setState({ isDeviceGenuine: isGenuine }) this.props.updateGenuineCheck({
isDeviceGenuine: isGenuine,
})
} }
redoGenuineCheck = () => { redoGenuineCheck = () => {
this.props.setGenuineCheckFail(false) this.props.updateGenuineCheck({ isGenuineFail: false })
} }
contactSupport = () => { contactSupport = () => {
console.log('contact support coming later') const contactSupportUrl =
'https://support.ledgerwallet.com/hc/en-us/requests/new?ticket_form_id=248165'
shell.openExternal(contactSupportUrl)
} }
renderGenuineFail = () => ( renderGenuineFail = () => (
@ -101,16 +112,10 @@ class GenuineCheck extends PureComponent<StepProps, State> {
render() { render() {
const { nextStep, prevStep, t, onboarding } = this.props const { nextStep, prevStep, t, onboarding } = this.props
const { const { genuine } = onboarding
pinStepPass, const { cachedPinStepButton, cachedRecoveryStepButton, isGenuineCheckModalOpened } = this.state
phraseStepPass,
cachedPinStepButton,
cachedPhraseStepButton,
isGenuineCheckModalOpened,
isDeviceGenuine,
} = this.state
if (onboarding.isGenuineFail) { if (genuine.isGenuineFail) {
return this.renderGenuineFail() return this.renderGenuineFail()
} }
@ -137,7 +142,7 @@ class GenuineCheck extends PureComponent<StepProps, State> {
</CardWrapper> </CardWrapper>
</Box> </Box>
<Box mt={5}> <Box mt={5}>
<CardWrapper isDisabled={!pinStepPass}> <CardWrapper isDisabled={!genuine.pinStepPass}>
<Box justify="center"> <Box justify="center">
<Box horizontal> <Box horizontal>
<IconOptionRow>2.</IconOptionRow> <IconOptionRow>2.</IconOptionRow>
@ -148,13 +153,13 @@ class GenuineCheck extends PureComponent<StepProps, State> {
<RadioGroup <RadioGroup
style={{ margin: '0 30px' }} style={{ margin: '0 30px' }}
items={this.getButtonLabel()} items={this.getButtonLabel()}
activeKey={cachedPhraseStepButton} activeKey={cachedRecoveryStepButton}
onChange={item => this.handleButtonPass(item, 'phraseStepPass')} onChange={item => this.handleButtonPass(item, 'recoveryStepPass')}
/> />
</CardWrapper> </CardWrapper>
</Box> </Box>
<Box mt={5}> <Box mt={5}>
<CardWrapper isDisabled={!phraseStepPass}> <CardWrapper isDisabled={!genuine.recoveryStepPass}>
<Box justify="center"> <Box justify="center">
<Box horizontal> <Box horizontal>
<IconOptionRow>3.</IconOptionRow> <IconOptionRow>3.</IconOptionRow>
@ -166,10 +171,10 @@ class GenuineCheck extends PureComponent<StepProps, State> {
<Button <Button
big big
primary primary
disabled={!phraseStepPass} disabled={!genuine.recoveryStepPass}
onClick={this.handleOpenGenuineCheckModal} onClick={this.handleOpenGenuineCheckModal}
> >
{isDeviceGenuine ? ( {genuine.isDeviceGenuine ? (
<Box horizontal align="center" flow={1}> <Box horizontal align="center" flow={1}>
<IconCheck size={16} /> <IconCheck size={16} />
<span>{t('onboarding:genuineCheck.buttons.tryAgain')}</span> <span>{t('onboarding:genuineCheck.buttons.tryAgain')}</span>
@ -189,6 +194,7 @@ class GenuineCheck extends PureComponent<StepProps, State> {
t={t} t={t}
nextStep={nextStep} nextStep={nextStep}
prevStep={prevStep} prevStep={prevStep}
isContinueDisabled={!genuine.isDeviceGenuine}
/> />
<GenuineCheckModal <GenuineCheckModal
isOpened={isGenuineCheckModalOpened} isOpened={isGenuineCheckModalOpened}

15
src/components/Onboarding/steps/Init.js

@ -2,10 +2,15 @@
import React from 'react' import React from 'react'
import { shell } from 'electron' import { shell } from 'electron'
import { colors } from 'styles/theme'
import styled from 'styled-components' import styled from 'styled-components'
import Box, { Card } from 'components/base/Box' import Box, { Card } from 'components/base/Box'
import IconUser from 'icons/User' import IconUser from 'icons/User'
import IconAdd from 'icons/Plus'
import IconRecover from 'icons/Recover'
import IconCheck from 'icons/Check'
import IconExternalLink from 'icons/ExternalLink'
import IconChevronRight from 'icons/ChevronRight' import IconChevronRight from 'icons/ChevronRight'
import { Title } from '../helperComponents' import { Title } from '../helperComponents'
@ -16,28 +21,28 @@ export default (props: StepProps) => {
const optionCards = [ const optionCards = [
{ {
key: 'newDevice', key: 'newDevice',
icon: <IconUser size={22} />, icon: <IconAdd size={16} />,
title: t('onboarding:init.newDevice.title'), title: t('onboarding:init.newDevice.title'),
desc: t('onboarding:init.newDevice.desc'), desc: t('onboarding:init.newDevice.desc'),
onClick: () => nextStep(), onClick: () => nextStep(),
}, },
{ {
key: 'restoreDevice', key: 'restoreDevice',
icon: <IconUser size={22} />, icon: <IconRecover size={16} />,
title: t('onboarding:init.restoreDevice.title'), title: t('onboarding:init.restoreDevice.title'),
desc: t('onboarding:init.restoreDevice.desc'), desc: t('onboarding:init.restoreDevice.desc'),
onClick: () => jumpStep('choosePIN'), onClick: () => jumpStep('choosePIN'),
}, },
{ {
key: 'initializedDevice', key: 'initializedDevice',
icon: <IconUser size={22} />, icon: <IconCheck size={16} />,
title: t('onboarding:init.initializedDevice.title'), title: t('onboarding:init.initializedDevice.title'),
desc: t('onboarding:init.initializedDevice.desc'), desc: t('onboarding:init.initializedDevice.desc'),
onClick: () => jumpStep('choosePIN'), onClick: () => jumpStep('choosePIN'),
}, },
{ {
key: 'noDevice', key: 'noDevice',
icon: <IconUser size={22} />, icon: <IconExternalLink size={16} />,
title: t('onboarding:init.noDevice.title'), title: t('onboarding:init.noDevice.title'),
desc: t('onboarding:init.noDevice.desc'), desc: t('onboarding:init.noDevice.desc'),
onClick: () => shell.openExternal('https://www.ledger.fr/'), onClick: () => shell.openExternal('https://www.ledger.fr/'),
@ -82,7 +87,7 @@ export function OptionFlowCard({ card }: { card: CardType }) {
}} }}
onClick={onClick} onClick={onClick}
> >
<Box justify="center" color="grey" style={{ width: 50 }}> <Box justify="center" style={{ width: 50, color: colors.wallet }}>
{icon} {icon}
</Box> </Box>
<Box ff="Open Sans|Regular" justify="center" fontSize={4} grow> <Box ff="Open Sans|Regular" justify="center" fontSize={4} grow>

2
src/components/RecipientAddress/index.js

@ -45,7 +45,7 @@ const BackgroundLayer = styled(Box)`
type Props = { type Props = {
value: string, value: string,
// return false if it can't be changed (invalid info) // return false if it can't be changed (invalid info)
onChange: (string, { amount?: number, currency?: CryptoCurrency }) => ?boolean, onChange: (string, ?{ amount?: number, currency?: CryptoCurrency }) => ?boolean,
withQrCode: boolean, withQrCode: boolean,
} }

2
src/components/base/FlipTicker/index.js

@ -51,7 +51,7 @@ class FlipTicker extends PureComponent<Props, State> {
const { height } = this.state const { height } = this.state
return ( return (
<Container innerRef={n => (this._node = n)} {...p}> <Container innerRef={n => (this._node = n)} {...p}>
{value.split('').map((l, i) => ( {[...value].map((l, i) => (
<Box key={i}> <Box key={i}>
{!/[0-9]/.test(l) ? ( {!/[0-9]/.test(l) ? (
l === ' ' ? ( l === ' ' ? (

6
src/components/modals/Send/01-step-amount.js

@ -17,6 +17,7 @@ const AccountField = ({ onChange, value, t }: *) => (
</Box> </Box>
) )
// TODO we should use isRecipientValid & provide a feedback to user
const RecipientField = ({ bridge, account, transaction, onChangeTransaction, t }: *) => ( const RecipientField = ({ bridge, account, transaction, onChangeTransaction, t }: *) => (
<Box flow={1}> <Box flow={1}>
<Label> <Label>
@ -26,9 +27,8 @@ const RecipientField = ({ bridge, account, transaction, onChangeTransaction, t }
<RecipientAddress <RecipientAddress
withQrCode withQrCode
value={bridge.getTransactionRecipient(account, transaction)} value={bridge.getTransactionRecipient(account, transaction)}
onChange={(recipient, { amount, currency }) => { onChange={(recipient, maybeExtra) => {
console.log(recipient, amount, currency, account.currency) const { amount, currency } = maybeExtra || {}
// TODO we should use isRecipientValid & provide a feedback to user
if (currency && currency.scheme !== account.currency.scheme) return false if (currency && currency.scheme !== account.currency.scheme) return false
let t = transaction let t = transaction
if (amount) { if (amount) {

13
src/helpers/apps/listApps.js

@ -1,10 +1,19 @@
// @flow // @flow
import axios from 'axios' import axios from 'axios'
export default async () => { const { API_BASE_URL } = process.env
export default async (targetId: string | number) => {
try { try {
const { data: deviceData } = await axios.get(
`${API_BASE_URL}/device_versions_target_id/${targetId}`,
)
const { data } = await axios.get('https://api.ledgerwallet.com/update/applications') const { data } = await axios.get('https://api.ledgerwallet.com/update/applications')
if (deviceData.name in data) {
return data[deviceData.name]
}
return data['nanos-1.4'] return data['nanos-1.4']
} catch (err) { } catch (err) {
const error = Error(err.message) const error = Error(err.message)

1
src/helpers/ipc.js

@ -41,6 +41,7 @@ export class Command<In, A> {
}) })
}, },
error: error => { error: error => {
console.log('exec error:', error)
send({ send({
type: `ERROR_${requestId}`, type: `ERROR_${requestId}`,
data: { data: {

6
src/internals/accounts/scanAccountsOnDevice.js → src/helpers/libcore.js

@ -18,10 +18,10 @@ import type { NJSAccount, NJSOperation } from '@ledgerhq/ledger-core/src/ledgerc
type Props = { type Props = {
devicePath: string, devicePath: string,
currencyId: string, currencyId: string,
onAccountScanned: Function, onAccountScanned: AccountRaw => void,
} }
export default function scanAccountsOnDevice(props: Props): Promise<AccountRaw[]> { export function scanAccountsOnDevice(props: Props): Promise<AccountRaw[]> {
const { devicePath, currencyId, onAccountScanned } = props const { devicePath, currencyId, onAccountScanned } = props
return withDevice(devicePath)(async transport => { return withDevice(devicePath)(async transport => {
@ -269,7 +269,7 @@ async function buildAccountRaw({
freshAddressPath, freshAddressPath,
balance, balance,
blockHeight, blockHeight,
archived: false, archived: true,
index: accountIndex, index: accountIndex,
operations, operations,
pendingOperations: [], pendingOperations: [],

16
src/icons/Recover.js

@ -0,0 +1,16 @@
// @flow
import React from 'react'
const path = (
<path
fill="currentColor"
d="M15.65 7.985c.008 4.27-3.475 7.762-7.745 7.765a7.722 7.722 0 0 1-5.2-1.998.375.375 0 0 1-.013-.544l.53-.53a.376.376 0 0 1 .517-.012A6.228 6.228 0 0 0 7.9 14.25 6.247 6.247 0 0 0 14.15 8 6.247 6.247 0 0 0 7.9 1.75a6.23 6.23 0 0 0-4.434 1.845L5 5.108a.375.375 0 0 1-.264.642H.725a.375.375 0 0 1-.375-.375V1.42c0-.333.402-.5.638-.267l1.41 1.39A7.75 7.75 0 0 1 15.65 7.985zm-5.22 2.818l.44-.606a.375.375 0 0 0-.082-.524L8.65 8.118V3.625a.375.375 0 0 0-.375-.375h-.75a.375.375 0 0 0-.375.375v5.257l2.755 2.004a.375.375 0 0 0 .524-.083z"
/>
)
export default ({ size, ...p }: { size: number }) => (
<svg viewBox="0 0 16 16" height={size} width={size} {...p}>
{path}
</svg>
)

48
src/internals/accounts/index.js

@ -1,48 +0,0 @@
// @flow
import type { IPCSend } from 'types/electron'
import scanAccountsOnDevice from './scanAccountsOnDevice'
import signAndBroadcastTransactionBTCLike from './signAndBroadcastTransaction/btc'
import sync from './sync'
export default {
sync,
signAndBroadcastTransactionBTCLike,
scan: async (
send: IPCSend,
{
devicePath,
currencyId,
}: {
devicePath: string,
currencyId: string,
},
) => {
try {
send('accounts.scanAccountsOnDevice.start', { pid: process.pid }, { kill: false })
const accounts = await scanAccountsOnDevice({
devicePath,
currencyId,
onAccountScanned: account => {
send('accounts.scanAccountsOnDevice.accountScanned', account, { kill: false })
},
})
send('accounts.scanAccountsOnDevice.success', accounts)
} catch (err) {
send('accounts.scanAccountsOnDevice.fail', formatErr(err))
}
},
}
// TODO: move this to a helper
function formatErr(err) {
if (err instanceof Error) {
return err.message || err.code
}
if (typeof err === 'string') {
return err
}
return 'unknown error'
}

71
src/internals/accounts/signAndBroadcastTransaction/btc.js

@ -1,71 +0,0 @@
// @flow
import Btc from '@ledgerhq/hw-app-btc'
import { withDevice } from 'helpers/deviceAccess'
import type { AccountRaw } from '@ledgerhq/live-common/lib/types'
import type { IPCSend } from 'types/electron'
import { getWalletIdentifier } from '../scanAccountsOnDevice'
type BitcoinLikeTransaction = {
amount: number,
feePerByte: number,
recipient: string,
}
export default async function signAndBroadcastTransactionBTCLike(
send: IPCSend,
{
account,
transaction,
deviceId, // which is in fact `devicePath`
}: {
account: AccountRaw,
transaction: BitcoinLikeTransaction,
deviceId: string,
},
) {
try {
// TODO: investigate why importing it on file scope causes trouble
const core = require('init-ledger-core')()
const txHash = await withDevice(deviceId)(async transport => {
const hwApp = new Btc(transport)
const WALLET_IDENTIFIER = await getWalletIdentifier({
hwApp,
isSegwit: !!account.isSegwit,
currencyId: account.currencyId,
devicePath: deviceId,
})
const njsWallet = await core.getWallet(WALLET_IDENTIFIER)
const njsAccount = await njsWallet.getAccount(account.index)
const bitcoinLikeAccount = njsAccount.asBitcoinLikeAccount()
const njsWalletCurrency = njsWallet.getCurrency()
const amount = core.createAmount(njsWalletCurrency, transaction.amount)
const fees = core.createAmount(njsWalletCurrency, transaction.feePerByte)
const transactionBuilder = bitcoinLikeAccount.buildTransaction()
// TODO: check if is valid address. if not, it will fail silently on invalid
transactionBuilder.sendToAddress(amount, transaction.recipient)
// TODO: don't use hardcoded value for sequence (and first also maybe)
transactionBuilder.pickInputs(0, 0xffffff)
transactionBuilder.setFeesPerByte(fees)
const builded = await transactionBuilder.build()
const signedTransaction = await core.signTransaction(hwApp, builded)
const txHash = await njsAccount
.asBitcoinLikeAccount()
.broadcastRawTransaction(signedTransaction)
return txHash
})
send('accounts.signAndBroadcastTransactionBTCLike.success', txHash)
} catch (err) {
send('accounts.signAndBroadcastTransactionBTCLike.fail', err)
}
}

7
src/internals/accounts/sync.js

@ -1,7 +0,0 @@
// @flow
import type { IPCSend } from 'types/electron'
export default (send: IPCSend) => {
setTimeout(() => send('accounts.sync.success'), 5e3)
}

6
src/internals/devices/index.js

@ -1,6 +1,8 @@
// @flow // @flow
import type { Command } from 'helpers/ipc' import type { Command } from 'helpers/ipc'
import libcoreScanAccounts from 'commands/libcoreScanAccounts'
import libcoreSignAndBroadcast from 'commands/libcoreSignAndBroadcast'
import getAddress from 'commands/getAddress' import getAddress from 'commands/getAddress'
import signTransaction from 'commands/signTransaction' import signTransaction from 'commands/signTransaction'
import getDeviceInfo from 'commands/getDeviceInfo' import getDeviceInfo from 'commands/getDeviceInfo'
@ -13,6 +15,7 @@ import uninstallApp from 'commands/uninstallApp'
import installOsuFirmware from 'commands/installOsuFirmware' import installOsuFirmware from 'commands/installOsuFirmware'
import installFinalFirmware from 'commands/installFinalFirmware' import installFinalFirmware from 'commands/installFinalFirmware'
import installMcu from 'commands/installMcu' import installMcu from 'commands/installMcu'
import listApps from 'commands/listApps'
export const commands: Array<Command<any, any>> = [ export const commands: Array<Command<any, any>> = [
getAddress, getAddress,
@ -22,9 +25,12 @@ export const commands: Array<Command<any, any>> = [
getIsGenuine, getIsGenuine,
getLatestFirmwareForDevice, getLatestFirmwareForDevice,
installApp, installApp,
libcoreScanAccounts,
libcoreSignAndBroadcast,
listenDevices, listenDevices,
uninstallApp, uninstallApp,
installOsuFirmware, installOsuFirmware,
installFinalFirmware, installFinalFirmware,
installMcu, installMcu,
listApps,
] ]

3
src/internals/manager/index.js

@ -1,7 +1,6 @@
// @flow // @flow
import type { Command } from 'helpers/ipc' import type { Command } from 'helpers/ipc'
import listApps from 'commands/listApps'
import getMemInfo from 'commands/getMemInfo' import getMemInfo from 'commands/getMemInfo'
/** /**
@ -20,4 +19,4 @@ import getMemInfo from 'commands/getMemInfo'
* *
*/ */
export const commands: Array<Command<any, any>> = [listApps, getMemInfo] export const commands: Array<Command<any, any>> = [getMemInfo]

27
src/reducers/onboarding.js

@ -17,14 +17,24 @@ export type OnboardingState = {
stepIndex: number, stepIndex: number,
stepName: string, // TODO: specify that the string comes from Steps type stepName: string, // TODO: specify that the string comes from Steps type
steps: Step[], steps: Step[],
isGenuineFail: boolean, genuine: {
pinStepPass: boolean,
recoveryStepPass: boolean,
isGenuineFail: boolean,
isDeviceGenuine: boolean,
},
isLedgerNano: boolean, isLedgerNano: boolean,
} }
const state: OnboardingState = { const state: OnboardingState = {
stepIndex: 0, stepIndex: 0,
stepName: 'start', stepName: process.env.SKIP_ONBOARDING ? 'finish' : 'start',
isGenuineFail: false, genuine: {
pinStepPass: false,
recoveryStepPass: false,
isGenuineFail: false,
isDeviceGenuine: false,
},
isLedgerNano: true, isLedgerNano: true,
steps: [ steps: [
{ {
@ -143,10 +153,15 @@ const handlers = {
const index = state.steps.indexOf(step) const index = state.steps.indexOf(step)
return { ...state, stepName: step.name, stepIndex: index } return { ...state, stepName: step.name, stepIndex: index }
}, },
ONBOARDING_SET_GENUINE_CHECK_FAIL: (state, { payload: isGenuineFail }) => ({
UPDATE_GENUINE_CHECK: (state, { payload: obj }) => ({
...state, ...state,
isGenuineFail, genuine: {
...state.genuine,
...obj,
},
}), }),
ONBOARDING_SET_DEVICE_TYPE: (state, { payload: isLedgerNano }) => ({ ONBOARDING_SET_DEVICE_TYPE: (state, { payload: isLedgerNano }) => ({
...state, ...state,
isLedgerNano, isLedgerNano,
@ -158,5 +173,5 @@ export default handleActions(handlers, state)
export const nextStep = createAction('ONBOARDING_NEXT_STEP') export const nextStep = createAction('ONBOARDING_NEXT_STEP')
export const prevStep = createAction('ONBOARDING_PREV_STEP') export const prevStep = createAction('ONBOARDING_PREV_STEP')
export const jumpStep = createAction('ONBOARDING_JUMP_STEP') export const jumpStep = createAction('ONBOARDING_JUMP_STEP')
export const setGenuineCheckFail = createAction('ONBOARDING_SET_GENUINE_CHECK_FAIL') export const updateGenuineCheck = createAction('UPDATE_GENUINE_CHECK')
export const isLedgerNano = createAction('ONBOARDING_SET_DEVICE_TYPE') export const isLedgerNano = createAction('ONBOARDING_SET_DEVICE_TYPE')

2
src/reducers/settings.js

@ -30,6 +30,7 @@ export type SettingsState = {
}, },
region: string, region: string,
developerMode: boolean, developerMode: boolean,
shareAnalytics: boolean,
} }
/* have to check if available for all OS */ /* have to check if available for all OS */
@ -69,6 +70,7 @@ const state: SettingsState = {
region, region,
developerMode: false, developerMode: false,
loaded: false, loaded: false,
shareAnalytics: false,
} }
function asCryptoCurrency(c: Currency): ?CryptoCurrency { function asCryptoCurrency(c: Currency): ?CryptoCurrency {

8
static/i18n/en/onboarding.yml

@ -82,12 +82,12 @@ setPassword:
analytics: analytics:
title: Help Ledger to improve its products and services title: Help Ledger to improve its products and services
desc: This is a long text, please replace it with the final wording once it’s done.
Lorem ipsum dolor amet ledger lorem dolor ipsum amet desc: This is a long text, please replace it with the final wording once it’s done.
Lorem ipsum dolor amet ledger lorem dolor ipsum amet
shareDiagnostics: shareAnalytics:
title: Share analytics
desc: Help Ledger improve its products and services by automatically sending diagnostics and usage data.
shareData:
title: Share analytics title: Share analytics
desc: Help Ledger improve its products and services by automatically sending diagnostics and usage data. desc: Help Ledger improve its products and services by automatically sending diagnostics and usage data.
termsConditions:
title: Terms and Conditions
desc: Please accept terms and conditions to proceed
finish: finish:
title: This is the title of the screen. 1 line is the maximum title: This is the title of the screen. 1 line is the maximum
desc: This is a long text, please replace it with the final wording once it’s done.
Lorem ipsum dolor amet ledger lorem dolor ipsum amet desc: This is a long text, please replace it with the final wording once it’s done.
Lorem ipsum dolor amet ledger lorem dolor ipsum amet

12
yarn.lock

@ -1451,9 +1451,9 @@
dependencies: dependencies:
events "^2.0.0" events "^2.0.0"
"@ledgerhq/ledger-core@^1.2.1": "@ledgerhq/ledger-core@^1.3.0":
version "1.2.1" version "1.3.0"
resolved "https://registry.yarnpkg.com/@ledgerhq/ledger-core/-/ledger-core-1.2.1.tgz#8176a4fa9182d8e0fe4456cbdc4701e4e6e25145" resolved "https://registry.yarnpkg.com/@ledgerhq/ledger-core/-/ledger-core-1.3.0.tgz#2b26d43c4a8973e00e0c6671a2da5183ef0ce229"
dependencies: dependencies:
"@ledgerhq/hw-app-btc" "^4.7.3" "@ledgerhq/hw-app-btc" "^4.7.3"
"@ledgerhq/hw-transport-node-hid" "^4.7.6" "@ledgerhq/hw-transport-node-hid" "^4.7.6"
@ -1464,9 +1464,9 @@
npm "^5.7.1" npm "^5.7.1"
prebuild-install "^2.2.2" prebuild-install "^2.2.2"
"@ledgerhq/live-common@^2.9.1": "@ledgerhq/live-common@2.11.0":
version "2.9.2" version "2.11.0"
resolved "https://registry.yarnpkg.com/@ledgerhq/live-common/-/live-common-2.9.2.tgz#e77139368c690fc542cb95cdb87ea37890652377" resolved "https://registry.yarnpkg.com/@ledgerhq/live-common/-/live-common-2.11.0.tgz#9b8af8b76aab4094ec2189538f8330711a3b91a3"
dependencies: dependencies:
axios "^0.18.0" axios "^0.18.0"
invariant "^2.2.2" invariant "^2.2.2"

Loading…
Cancel
Save