Browse Source

Improved OnProgress for ImportAccounts

master
Loëck Vézien 7 years ago
parent
commit
350ad7c1a2
No known key found for this signature in database GPG Key ID: CBCDCE384E853AC4
  1. 22
      package.json
  2. 14
      src/components/modals/AddAccount/ImportAccounts.js
  3. 55
      src/components/modals/AddAccount/index.js
  4. 26
      src/components/modals/Receive.js
  5. 23
      src/helpers/btc.js
  6. 42
      src/helpers/db.js
  7. 63
      src/internals/usb/wallet/accounts.js
  8. 14
      src/internals/usb/wallet/index.js
  9. 7
      src/main/bridge.js
  10. 656
      yarn.lock

22
package.json

@ -48,7 +48,7 @@
"@fortawesome/react-fontawesome": "^0.0.17",
"@ledgerhq/common": "^4.2.0",
"@ledgerhq/currencies": "^4.2.0",
"@ledgerhq/hw-app-btc": "^4.2.0",
"@ledgerhq/hw-app-btc": "^4.2.1",
"@ledgerhq/hw-app-eth": "^4.2.0",
"@ledgerhq/hw-transport": "^4.2.0",
"@ledgerhq/hw-transport-node-hid": "^4.2.0",
@ -97,12 +97,12 @@
"tippy.js": "^2.2.3"
},
"devDependencies": {
"@storybook/addon-actions": "^3.3.13",
"@storybook/addon-knobs": "^3.3.13",
"@storybook/addon-links": "^3.3.13",
"@storybook/addon-options": "^3.3.13",
"@storybook/addons": "^3.3.13",
"@storybook/react": "^3.3.13",
"@storybook/addon-actions": "^3.3.14",
"@storybook/addon-knobs": "^3.3.14",
"@storybook/addon-links": "^3.3.14",
"@storybook/addon-options": "^3.3.14",
"@storybook/addons": "^3.3.14",
"@storybook/react": "^3.3.14",
"babel-core": "^6.26.0",
"babel-eslint": "^8.2.1",
"babel-loader": "^7.1.2",
@ -118,15 +118,15 @@
"concurrently": "^3.5.1",
"dotenv": "^5.0.0",
"electron": "1.8.2",
"electron-builder": "^20.0.7",
"electron-builder": "^20.0.8",
"electron-devtools-installer": "^2.2.3",
"electron-webpack": "1.13.0",
"eslint": "^4.18.1",
"eslint-config-airbnb": "^16.1.0",
"eslint-config-prettier": "^2.9.0",
"eslint-import-resolver-babel-module": "^4.0.0",
"eslint-plugin-flowtype": "^2.45.0",
"eslint-plugin-import": "^2.8.0",
"eslint-plugin-flowtype": "^2.46.0",
"eslint-plugin-import": "^2.9.0",
"eslint-plugin-jsx-a11y": "^6.0.3",
"eslint-plugin-react": "^7.7.0",
"flow-bin": "^0.66.0",
@ -134,7 +134,7 @@
"hard-source-webpack-plugin": "^0.5.18",
"husky": "^0.14.3",
"js-yaml": "^3.10.0",
"lint-staged": "^6.1.1",
"lint-staged": "^7.0.0",
"node-loader": "^0.6.0",
"prettier": "^1.10.2",
"react-hot-loader": "^4.0.0-beta.21",

14
src/components/modals/AddAccount/ImportAccounts.js

@ -5,11 +5,10 @@ import { translate } from 'react-i18next'
import type { T } from 'types/common'
import { formatBTC } from 'helpers/format'
import Box from 'components/base/Box'
import Button from 'components/base/Button'
import CheckBox from 'components/base/CheckBox'
import FormattedVal from 'components/base/FormattedVal'
import Input from 'components/base/Input'
type Props = {
@ -101,7 +100,16 @@ class ImportAccounts extends PureComponent<Props, State> {
onChange={this.handleChangeInput(account.id)}
/>
</Box>
<Box>Balance: {formatBTC(account.balance)}</Box>
<Box>
Balance:{' '}
<FormattedVal
alwaysShowSign={false}
color="dark"
unit={account.unit}
showCode
val={account.balance}
/>
</Box>
<Box>Transactions: {account.transactions.length}</Box>
</Box>
</Box>

55
src/components/modals/AddAccount/index.js

@ -23,11 +23,12 @@ import { sendEvent } from 'renderer/events'
import { addAccount, updateAccount } from 'actions/accounts'
import Box from 'components/base/Box'
import Text from 'components/base/Text'
import Button from 'components/base/Button'
import FormattedVal from 'components/base/FormattedVal'
import Label from 'components/base/Label'
import Modal, { ModalBody } from 'components/base/Modal'
import Select from 'components/base/Select'
import Text from 'components/base/Text'
import CreateAccount from './CreateAccount'
import ImportAccounts from './ImportAccounts'
@ -67,12 +68,24 @@ const Steps = {
<Box>Start {props.currency.name} App on your Ledger: ko</Box>
</Box>
),
inProgress: (props: Object) => (
inProgress: ({ progress, unit }: Object) => (
<Box>
In progress.
{props.progress !== null && (
{progress !== null && (
<Box>
Account: {props.progress.account} / Transactions: {props.progress.transactions}
<Box>Account: {progress.account}</Box>
<Box>
Balance:{' '}
<FormattedVal
alwaysShowSign={false}
color="dark"
unit={unit}
showCode
val={progress.balance || 0}
/>
</Box>
<Box>Transactions: {progress.transactions || 0}</Box>
{progress.success && <Box>Finish ! Next account in progress...</Box>}
</Box>
)}
</Box>
@ -143,7 +156,7 @@ class AddAccountModal extends PureComponent<Props, State> {
}
componentDidMount() {
ipcRenderer.on('msg', this.handleWalletRequest)
ipcRenderer.on('msg', this.handleMsgEvent)
}
componentWillReceiveProps(nextProps) {
@ -166,7 +179,7 @@ class AddAccountModal extends PureComponent<Props, State> {
}
componentWillUnmount() {
ipcRenderer.removeListener('msg', this.handleWalletRequest)
ipcRenderer.removeListener('msg', this.handleMsgEvent)
clearTimeout(this._timeout)
}
@ -206,6 +219,7 @@ class AddAccountModal extends PureComponent<Props, State> {
...props(step === 'inProgress', {
t,
progress,
unit: currency !== null && getDefaultUnitByCoinType(currency.coinType),
}),
...props(step === 'listAccounts', {
t,
@ -219,12 +233,24 @@ class AddAccountModal extends PureComponent<Props, State> {
}
}
handleWalletRequest = (e, { data, type }) => {
handleMsgEvent = (e, { data, type }) => {
if (type === 'wallet.getAccounts.start') {
this._pid = data.pid
}
if (type === 'wallet.getAccounts.progress') {
this.setState({
this.setState(prev => ({
step: 'inProgress',
progress: data,
})
progress:
prev.progress === null
? data
: prev.progress.success
? data
: {
...prev.progress,
...data,
},
}))
}
if (type === 'wallet.getAccounts.fail') {
@ -253,7 +279,12 @@ class AddAccountModal extends PureComponent<Props, State> {
})
}
handleClose = () => clearTimeout(this._timeout)
handleClose = () => {
sendEvent('msg', 'kill.process', {
pid: this._pid,
})
clearTimeout(this._timeout)
}
handleHide = () =>
this.setState({
@ -277,6 +308,7 @@ class AddAccountModal extends PureComponent<Props, State> {
}
_timeout = undefined
_pid = null
render() {
const { step } = this.state
@ -286,6 +318,7 @@ class AddAccountModal extends PureComponent<Props, State> {
<Modal
name={MODAL_ADD_ACCOUNT}
preventBackdropClick={step !== 'chooseWallet'}
onClose={this.handleClose}
onHide={this.handleHide}
render={({ onClose }) => {
const Step = Steps[step]

26
src/components/modals/Receive.js

@ -1,6 +1,6 @@
// @flow
import React, { PureComponent } from 'react'
import React, { PureComponent, Fragment } from 'react'
import { translate } from 'react-i18next'
import get from 'lodash/get'
@ -72,16 +72,20 @@ class ReceiveModal extends PureComponent<Props, State> {
<Label>Account</Label>
<SelectAccount value={account} onChange={this.handleChangeInput('account')} />
</Box>
<Box flow={1}>
<Label>Request amount</Label>
<Input
type="number"
min={0}
max={account.balance / 1e8}
onChange={this.handleChangeInput('amount')}
/>
</Box>
<ReceiveBox path={account.path} amount={amount} address={account.address || ''} />
{account && (
<Fragment>
<Box flow={1}>
<Label>Request amount</Label>
<Input
type="number"
min={0}
max={account.balance / 1e8}
onChange={this.handleChangeInput('amount')}
/>
</Box>
<ReceiveBox path={account.path} amount={amount} address={account.address || ''} />
</Fragment>
)}
<Box horizontal justifyContent="center">
<Button primary onClick={onClose}>
Close

23
src/helpers/btc.js

@ -2,6 +2,7 @@
import ledger from 'ledger-test-library'
import bitcoin from 'bitcoinjs-lib'
import noop from 'lodash/noop'
export const networks = [
{
@ -42,6 +43,7 @@ export async function getAccount({
segwit,
network,
asyncDelay = 500,
onProgress = noop,
}: {
allAddresses?: Array<string>,
currentIndex?: number,
@ -50,10 +52,12 @@ export async function getAccount({
segwit: boolean,
network: Object,
asyncDelay?: number,
onProgress?: Function,
}) {
const gapLimit = 20
const script = segwit ? parseInt(network.scriptHash, 10) : parseInt(network.pubKeyHash, 10)
let balance = 0
let transactions = []
let lastAddress = null
@ -116,18 +120,21 @@ export async function getAccount({
const hasTransactions = txs.length > 0
transactions = [...transactions, ...txs.map(computeTransaction(allAddresses))]
lastAddress = hasTransactions ? getLastAddress(addresses, txs[0]) : lastAddress
if (hasTransactions) {
const newTransactions = txs.map(computeTransaction(allAddresses))
lastAddress = getLastAddress(addresses, txs[0])
transactions = [...transactions, ...newTransactions]
balance = newTransactions.reduce((result, v) => result + v.balance, balance)
onProgress({
balance,
transactions: transactions.length,
})
return nextPath(index + (gapLimit - 1))
}
const balance = transactions.reduce((result, v) => {
result += v.balance
return result
}, 0)
const currentAddress =
lastAddress !== null ? lastAddress : getAddress({ type: 'external', index: 0 })

42
src/helpers/db.js

@ -4,7 +4,7 @@ import Store from 'electron-store'
import set from 'lodash/set'
import get from 'lodash/get'
import { getCurrencyByCoinType } from '@ledgerhq/currencies'
import { getDefaultUnitByCoinType, getCurrencyByCoinType } from '@ledgerhq/currencies'
import type { Accounts } from 'types/common'
@ -25,23 +25,29 @@ export function setEncryptionKey(key: DBKey, value?: string) {
encryptionKey[key] = value
}
export function serializeAccounts(accounts: Accounts) {
return accounts.map(account => ({
id: account.id,
address: account.address,
addresses: account.addresses,
balance: account.balance,
coinType: account.coinType,
currency: getCurrencyByCoinType(account.coinType),
index: account.index,
name: account.name,
path: account.path,
unit: account.unit,
transactions: account.transactions.map(t => ({
...t,
account,
})),
}))
export function serializeAccounts(accounts: Array<Object>) {
return accounts.map((account, key) => {
const a = {
id: account.id,
address: account.address,
addresses: account.addresses,
balance: account.balance,
coinType: account.coinType,
currency: getCurrencyByCoinType(account.coinType),
index: account.index,
name: account.name || `${key}`,
path: account.path,
unit: account.unit || getDefaultUnitByCoinType(account.coinType),
}
return {
...a,
transactions: account.transactions.map(t => ({
...t,
account: a,
})),
}
})
}
export function deserializeAccounts(accounts: Accounts) {

63
src/internals/usb/wallet/accounts.js

@ -7,8 +7,17 @@ import bs58check from 'bs58check'
import Btc from '@ledgerhq/hw-app-btc'
import { getAccount, getHDNode, networks } from 'helpers/btc'
import { serializeAccounts } from 'helpers/db'
type Coin = 0 | 1
type CoinType = 0 | 1
async function sleep(delay, callback) {
if (delay !== 0) {
await new Promise(resolve => setTimeout(resolve, delay))
}
return callback()
}
function getCompressPublicKey(publicKey) {
let compressedKeyIndex
@ -47,8 +56,16 @@ function encodeBase58Check(vchIn) {
return bs58check.encode(Buffer.from(vchIn))
}
function getPath({ coin, account, segwit }: { coin: Coin, account?: any, segwit: boolean }) {
return `${segwit ? 49 : 44}'/${coin}'${account !== undefined ? `/${account}'` : ''}`
function getPath({
coinType,
account,
segwit,
}: {
coinType: CoinType,
account?: any,
segwit: boolean,
}) {
return `${segwit ? 49 : 44}'/${coinType}'${account !== undefined ? `/${account}'` : ''}`
}
export function verifyAddress({
@ -69,18 +86,20 @@ export default async ({
transport,
currentAccounts,
onProgress,
coin = 1,
coinType = 1,
segwit = true,
nextAccountDelay = 1e3,
}: {
transport: Object,
currentAccounts: Array<*>,
onProgress: Function,
coin?: Coin,
coinType?: CoinType,
segwit?: boolean,
nextAccountDelay?: number,
}) => {
const btc = new Btc(transport)
const network = networks[coin]
const network = networks[coinType]
const [p2pkh, p2sh, fam] = [network.pubKeyHash, network.scriptHash, network.family].map(v =>
v.toString(16).padStart(4, 0),
@ -91,7 +110,7 @@ export default async ({
const getPublicKey = path => btc.getWalletPublicKey(path)
let result = bitcoin.crypto.sha256(
await getPublicKey(getPath({ segwit, coin })).then(
await getPublicKey(getPath({ segwit, coinType })).then(
({ publicKey }) => new Uint8Array(parseHexString(getCompressPublicKey(publicKey))),
),
)
@ -120,7 +139,7 @@ export default async ({
}
const getAllAccounts = async (currentAccount = 0, accounts = []) => {
const path = getPath({ segwit, coin, account: currentAccount })
const path = getPath({ segwit, coinType, account: currentAccount })
const xpub58 = await getXpub58ByPath({ path, account: currentAccount, network })
if (currentAccounts.includes(xpub58)) {
@ -128,25 +147,39 @@ export default async ({
}
const hdnode = getHDNode({ xpub58, network })
const account = await getAccount({ path, hdnode, network, segwit, asyncDelay: 0 })
onProgress({
account: currentAccount,
transactions: account.transactions.length,
const account = await getAccount({
path,
hdnode,
network,
segwit,
asyncDelay: 0,
onProgress: ({ transactions, ...progress }) =>
transactions > 0 && onProgress({ account: currentAccount, transactions, ...progress }),
})
const hasTransactions = account.transactions.length > 0
accounts.push({
id: xpub58,
coinType,
...account,
})
if (hasTransactions) {
return getAllAccounts(currentAccount + 1, accounts)
onProgress({
success: true,
})
const nextAccount = await sleep(nextAccountDelay, () =>
getAllAccounts(currentAccount + 1, accounts),
)
return nextAccount
}
return accounts
const result = await sleep(nextAccountDelay, () => accounts)
return serializeAccounts(result)
}
return getAllAccounts()

14
src/internals/usb/wallet/index.js

@ -9,10 +9,10 @@ async function getAllAccountsByCoinType({ pathDevice, coinType, currentAccounts,
// 1: BTC Testnet
if (coinType === 1) {
return getAllAccounts({ transport, currentAccounts, onProgress })
return getAllAccounts({ coinType, transport, currentAccounts, onProgress })
}
throw new Error('invalid coinType')
throw new Error('Invalid coinType')
}
export default (sendEvent: Function) => ({
@ -23,8 +23,16 @@ export default (sendEvent: Function) => ({
}: {
pathDevice: string,
coinType: number,
currentAccounts: Array<*>,
currentAccounts: Array<string>,
}) => {
sendEvent(
'wallet.getAccounts.start',
{
pid: process.pid,
},
{ kill: false },
)
try {
const data = await getAllAccountsByCoinType({
pathDevice,

7
src/main/bridge.js

@ -80,6 +80,13 @@ const handlers = {
init: setupAutoUpdater,
quitAndInstall,
},
kill: {
process: (send, { pid }) => {
try {
process.kill(pid, 'SIGINT')
} catch (e) {} // eslint-disable-line no-empty
},
},
}
ipcMain.on('msg', (event: any, payload) => {

656
yarn.lock

File diff suppressed because it is too large
Loading…
Cancel
Save