Browse Source

Merge pull request #42 from loeck/master

Add sync feature, change AppRegionDrag, add total balance
master
Meriadec Pillet 7 years ago
committed by GitHub
parent
commit
2a2045c150
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .storybook/addons.js
  2. 8
      .storybook/config.js
  3. 2
      flow-defs/module.js
  4. 4
      flow-defs/process.js
  5. 26
      package.json
  6. 22
      src/actions/accounts.js
  7. 21
      src/components/AccountPage.js
  8. 6
      src/components/AppRegionDrag.js
  9. 17
      src/components/DashboardPage.js
  10. 8
      src/components/SideBar/index.js
  11. 68
      src/components/TopBar.js
  12. 2
      src/components/base/Modal/stories.js
  13. 121
      src/components/modals/AddAccount.js
  14. 123
      src/helpers/btc.js
  15. 1
      src/internals/accounts/index.js
  16. 26
      src/internals/accounts/sync.js
  17. 29
      src/internals/index.js
  18. 28
      src/internals/usb/index.js
  19. 27
      src/internals/usb/wallet.js
  20. 142
      src/internals/usb/wallet/accounts.js
  21. 39
      src/internals/usb/wallet/index.js
  22. 8
      src/main/app.js
  23. 14
      src/main/bridge.js
  24. 30
      src/reducers/accounts.js
  25. 23
      src/renderer/events.js
  26. 7
      src/renderer/index.js
  27. 2
      src/types/common.js
  28. 5
      webpack/internals.config.js
  29. 273
      yarn.lock

2
.storybook/addons.js

@ -1,3 +1,3 @@
import '@storybook/addon-actions/register'
import '@storybook/addon-knobs/register'
import '@storybook/addon-actions/register'
import '@storybook/addon-options/register'

8
.storybook/config.js

@ -1,6 +1,7 @@
import React from 'react'
import { configure, addDecorator } from '@storybook/react'
import { withKnobs } from '@storybook/addon-knobs'
import { setOptions } from '@storybook/addon-options'
import { ThemeProvider } from 'styled-components'
import 'styles/global'
@ -19,4 +20,11 @@ addDecorator(story => (
addDecorator(withKnobs)
const { name, repository: url } = require('../package.json')
setOptions({
name,
url,
})
configure(loadStories, module)

2
flow-defs/module.js

@ -1,3 +1,5 @@
/* eslint-disable */
declare var module: {
hot: {
accept(path: string, callback: () => void): void,

4
flow-defs/process.js

@ -1,6 +1,10 @@
/* eslint-disable */
declare var process: {
send(args: any): void,
on(event: string, args: any): void,
nextTick(callback: Function): void,
setMaxListeners(any): void,
title: string,
env: Object,
}

26
package.json

@ -20,11 +20,7 @@
"storybook": "start-storybook -p 4444"
},
"lint-staged": {
"*.js": [
"eslint --fix",
"prettier --write",
"git add"
]
"*.js": ["eslint --fix", "prettier --write", "git add"]
},
"electronWebpack": {
"renderer": {
@ -42,8 +38,10 @@
"@ledgerhq/hw-app-eth": "^1.1.2-beta.068e2a14",
"@ledgerhq/hw-transport": "^1.1.2-beta.068e2a14",
"@ledgerhq/hw-transport-node-hid": "^1.1.2-beta.068e2a14",
"axios": "^0.17.1",
"bcryptjs": "^2.4.3",
"blockchain.info": "^2.11.0",
"bitcoinjs-lib": "^3.3.2",
"bs58check": "^2.1.1",
"color": "^2.0.1",
"downshift": "^1.25.0",
"electron-store": "^1.3.0",
@ -69,17 +67,17 @@
"redux-actions": "^2.2.1",
"redux-thunk": "^2.2.0",
"shortid": "^2.2.8",
"source-map-support": "^0.5.0",
"source-map-support": "^0.5.2",
"styled-components": "^2.2.4",
"styled-system": "^1.1.1"
},
"devDependencies": {
"@storybook/addon-actions": "^3.3.9",
"@storybook/addon-knobs": "^3.3.9",
"@storybook/addon-links": "^3.3.9",
"@storybook/addon-options": "^3.3.9",
"@storybook/addons": "^3.3.9",
"@storybook/react": "^3.3.9",
"@storybook/addon-actions": "^3.3.10",
"@storybook/addon-knobs": "^3.3.10",
"@storybook/addon-links": "^3.3.10",
"@storybook/addon-options": "^3.3.10",
"@storybook/addons": "^3.3.10",
"@storybook/react": "^3.3.10",
"babel-core": "^6.26.0",
"babel-eslint": "^8.2.1",
"babel-loader": "^7.1.2",
@ -94,7 +92,7 @@
"electron-builder": "^19.54.0",
"electron-devtools-installer": "^2.2.3",
"electron-webpack": "1.11.0",
"eslint": "^4.13.1",
"eslint": "^4.16.0",
"eslint-config-airbnb": "^16.1.0",
"eslint-config-prettier": "^2.9.0",
"eslint-import-resolver-babel-module": "^4.0.0",

22
src/actions/accounts.js

@ -1,6 +1,5 @@
// @flow
import values from 'lodash/values'
import { createAction } from 'redux-actions'
import type { Dispatch } from 'redux'
@ -8,10 +7,6 @@ import type { Dispatch } from 'redux'
import db from 'helpers/db'
import type { Account } from 'types/common'
import type { State } from 'reducers'
import { getAccounts } from 'reducers/accounts'
import { getAddressData } from 'helpers/btc'
export type AddAccount = Account => { type: string, payload: Account }
export const addAccount: AddAccount = payload => ({
@ -28,19 +23,6 @@ export const fetchAccounts: FetchAccounts = () => ({
const setAccountData = createAction('SET_ACCOUNT_DATA', (accountID, data) => ({ accountID, data }))
export const syncAccount: Function = account => async (dispatch: Dispatch<*>) => {
const { address } = account
const addressData = await getAddressData(address)
dispatch(setAccountData(account.id, addressData))
}
export const syncAccounts = () => async (dispatch: Dispatch<*>, getState: () => State) => {
const state = getState()
const accountsMap = getAccounts(state)
const accounts = values(accountsMap)
console.log(`syncing accounts...`)
await Promise.all(accounts.map(account => dispatch(syncAccount(account))))
console.log(`all accounts synced`)
const { id, ...data } = account
dispatch(setAccountData(id, data))
}

21
src/components/AccountPage.js

@ -2,11 +2,12 @@
import React, { PureComponent, Fragment } from 'react'
import { connect } from 'react-redux'
import { formatCurrencyUnit } from 'ledger-wallet-common/lib/data/currency'
import type { MapStateToProps } from 'react-redux'
import type { Account, AccountData } from 'types/common'
import { format } from 'helpers/btc'
import { getAccountById, getAccountData } from 'reducers/accounts'
import Box, { Card } from 'components/base/Box'
@ -22,20 +23,6 @@ const mapStateToProps: MapStateToProps<*, *, *> = (state, props) => ({
accountData: getAccountData(state, props.match.params.id),
})
function formatBTC(v) {
return formatCurrencyUnit(
{
name: 'bitcoin',
code: 'BTC',
symbol: 'b',
magnitude: 8,
},
v,
true,
true,
)
}
class AccountPage extends PureComponent<Props> {
render() {
const { account, accountData } = this.props
@ -49,7 +36,7 @@ class AccountPage extends PureComponent<Props> {
<Fragment>
<Box horizontal flow={3}>
<Box flex={1}>
<Card title="Balance">{formatBTC(accountData.balance)}</Card>
<Card title="Balance">{format(accountData.balance)}</Card>
</Box>
<Box flex={1}>
<Card title="Receive" />
@ -59,7 +46,7 @@ class AccountPage extends PureComponent<Props> {
{accountData.transactions.map(tr => (
<Box horizontal key={tr.hash}>
<Box grow>{'-'}</Box>
<Box>{formatBTC(tr.balance)}</Box>
<Box>{format(tr.balance)}</Box>
</Box>
))}
</Card>

6
src/components/AppRegionDrag.js

@ -4,10 +4,6 @@ import styled from 'styled-components'
export default styled.div`
-webkit-app-region: drag;
background: ${p => p.theme.colors.white};
height: 40px;
left: 0;
position: absolute;
right: 0;
top: 0;
z-index: -1;
`

17
src/components/DashboardPage.js

@ -4,28 +4,25 @@ import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
import type { MapStateToProps } from 'react-redux'
import type { Device } from 'types/common'
import { getCurrentDevice } from 'reducers/devices'
import { format } from 'helpers/btc'
import { getTotalBalance } from 'reducers/accounts'
import Box from 'components/base/Box'
const mapStateToProps: MapStateToProps<*, *, *> = state => ({
currentDevice: getCurrentDevice(state),
totalBalance: getTotalBalance(state),
})
type Props = {
currentDevice: Device | null,
totalBalance: number,
}
class DashboardPage extends PureComponent<Props> {
render() {
const { currentDevice } = this.props
return currentDevice !== null ? (
<Box style={{ wordBreak: 'break-word' }} p={20}>
Your current device: {currentDevice.path}
</Box>
) : null
const { totalBalance } = this.props
return <Box p={20}>Your balance: {format(totalBalance)}</Box>
}
}

8
src/components/SideBar/index.js

@ -10,6 +10,7 @@ import type { Accounts } from 'types/common'
import { openModal } from 'reducers/modals'
import { getAccounts } from 'reducers/accounts'
import { format } from 'helpers/btc'
import { rgba } from 'styles/helpers'
import Box, { GrowScroll } from 'components/base/Box'
@ -29,7 +30,6 @@ const Container = styled(Box).attrs({
noShrink: true,
})`
background-color: ${p => rgba(p.theme.colors[p.bg], process.platform === 'darwin' ? 0.4 : 1)};
padding-top: 40px;
width: 250px;
`
@ -77,11 +77,7 @@ class SideBar extends PureComponent<Props> {
<CapsSubtitle>{'Accounts'}</CapsSubtitle>
<div>
{Object.entries(accounts).map(([id, account]: [string, any]) => (
<Item
linkTo={`/account/${id}`}
desc={`${account.type.toUpperCase()} 3.78605936`}
key={id}
>
<Item linkTo={`/account/${id}`} desc={format(account.data.balance)} key={id}>
{account.name}
</Item>
))}

68
src/components/TopBar.js

@ -2,6 +2,7 @@
import React, { PureComponent, Fragment } from 'react'
import { connect } from 'react-redux'
import { ipcRenderer } from 'electron'
import type { MapStateToProps, MapDispatchToProps } from 'react-redux'
import type { Device, Devices } from 'types/common'
@ -35,6 +36,10 @@ type Props = {
}
type State = {
changeDevice: boolean,
sync: {
progress: null | boolean,
fail: boolean,
},
}
const hasDevices = props => props.currentDevice === null && props.devices.length > 0
@ -42,6 +47,14 @@ const hasDevices = props => props.currentDevice === null && props.devices.length
class TopBar extends PureComponent<Props, State> {
state = {
changeDevice: hasDevices(this.props),
sync: {
progress: null,
fail: false,
},
}
componentDidMount() {
ipcRenderer.on('msg', this.handleAccountSync)
}
componentWillReceiveProps(nextProps) {
@ -52,6 +65,39 @@ class TopBar extends PureComponent<Props, State> {
}
}
componentWillUnmount() {
ipcRenderer.removeListener('msg', this.handleAccountSync)
}
handleAccountSync = (e, { type }) => {
if (type === 'accounts.sync.progress') {
this.setState({
sync: {
progress: true,
fail: false,
},
})
}
if (type === 'accounts.sync.fail') {
this.setState({
sync: {
progress: null,
fail: true,
},
})
}
if (type === 'accounts.sync.success') {
this.setState({
sync: {
progress: false,
fail: false,
},
})
}
}
handleChangeDevice = () => {
const { devices } = this.props
@ -76,7 +122,7 @@ class TopBar extends PureComponent<Props, State> {
render() {
const { devices, hasPassword } = this.props
const { changeDevice } = this.state
const { changeDevice, sync } = this.state
return (
<Fragment>
@ -94,16 +140,16 @@ class TopBar extends PureComponent<Props, State> {
))}
</Overlay>
)}
<Box
bg="white"
noShrink
style={{ height: 60 }}
justify="flex-end"
align="center"
horizontal
>
{hasPassword && <LockApplication onLock={this.handleLock} />}
<CountDevices count={devices.length} onChangeDevice={this.handleChangeDevice} />
<Box bg="white" noShrink style={{ height: 60 }} align="center" horizontal>
<Box grow>
{sync.progress === true
? 'sync...'
: sync.fail === true ? 'sync fail :(' : 'sync finish!'}
</Box>
<Box justify="flex-end" horizontal>
{hasPassword && <LockApplication onLock={this.handleLock} />}
<CountDevices count={devices.length} onChangeDevice={this.handleChangeDevice} />
</Box>
</Box>
</Fragment>
)

2
src/components/base/Modal/stories.js

@ -7,7 +7,7 @@ import { Modal, ModalBody } from 'components/base/Modal'
const stories = storiesOf('Modal', module)
stories.add('basic', () => {
const isOpened = boolean('isOpened', false)
const isOpened = boolean('isOpened', true)
return (
<Modal
isOpened={isOpened}

121
src/components/modals/AddAccount.js

@ -3,13 +3,15 @@
import React, { PureComponent } from 'react'
import styled from 'styled-components'
import { connect } from 'react-redux'
import { ipcRenderer } from 'electron'
import type { MapStateToProps } from 'react-redux'
import type { Device } from 'types/common'
import type { Accounts, Device } from 'types/common'
import { sendSyncEvent } from 'renderer/events'
import { getCurrentDevice } from 'reducers/devices'
import { closeModal } from 'reducers/modals'
import { getAccounts } from 'reducers/accounts'
import { getCurrentDevice } from 'reducers/devices'
import { sendEvent } from 'renderer/events'
import { addAccount } from 'actions/accounts'
@ -49,12 +51,34 @@ const Steps = {
),
connectDevice: () => <div>Connect your Ledger</div>,
startWallet: (props: Object) => <div>Select {props.wallet.toUpperCase()} App on your Ledger</div>,
confirmation: (props: Object) => (
inProgress: (props: Object) => (
<div>
Add {props.wallet.toUpperCase()} - {props.accountName} - {props.walletAddress} ?
<Button onClick={props.onConfirm}>Yes!</Button>
In progress.
{props.progress !== null && (
<div>
Account: {props.progress.account} / Transactions: {props.progress.transactions}
</div>
)}
</div>
),
listAccounts: (props: Object) => {
const accounts = Object.entries(props.accounts)
return (
<div>
{accounts.length > 0
? accounts.map(([index, account]: [string, any]) => (
<div key={index}>
<div>Balance: {account.balance}</div>
<div>Transactions: {account.transactions.length}</div>
<div>
<Button onClick={props.onAddAccount(index)}>Import</Button>
</div>
</div>
))
: 'No accounts'}
</div>
)
},
}
type InputValue = {
@ -62,20 +86,23 @@ type InputValue = {
wallet: string,
}
type Step = 'createAccount' | 'connectDevice' | 'startWallet' | 'confirmation'
type Step = 'createAccount' | 'connectDevice' | 'inProgress' | 'startWallet' | 'listAccounts'
type Props = {
addAccount: Function,
closeModal: Function,
currentDevice: Device | null,
accounts: Accounts,
}
type State = {
inputValue: InputValue,
step: Step,
walletAddress: string,
accounts: Object,
progress: null | Object,
}
const mapStateToProps: MapStateToProps<*, *, *> = state => ({
accounts: getAccounts(state),
currentDevice: getCurrentDevice(state),
})
@ -89,7 +116,8 @@ const defaultState = {
accountName: '',
wallet: '',
},
walletAddress: '',
accounts: {},
progress: null,
step: 'createAccount',
}
@ -98,6 +126,10 @@ class AddAccountModal extends PureComponent<Props, State> {
...defaultState,
}
componentDidMount() {
ipcRenderer.on('msg', this.handleWalletRequest)
}
componentWillReceiveProps(nextProps) {
const { currentDevice } = nextProps
@ -119,33 +151,28 @@ class AddAccountModal extends PureComponent<Props, State> {
}
}
componentWillUnmount() {
ipcRenderer.removeListener('msg', this.handleWalletRequest)
clearTimeout(this._timeout)
}
getWalletInfos() {
const { inputValue } = this.state
const { currentDevice } = this.props
const { currentDevice, accounts } = this.props
if (currentDevice === null) {
return
}
const { data: { data }, type } = sendSyncEvent('usb', 'wallet.infos.request', {
sendEvent('usb', 'wallet.getAccounts', {
path: currentDevice.path,
wallet: inputValue.wallet,
currentAccounts: Object.keys(accounts),
})
if (type === 'wallet.infos.fail') {
this._timeout = setTimeout(() => this.getWalletInfos(), 1e3)
}
if (type === 'wallet.infos.success') {
this.setState({
walletAddress: data.bitcoinAddress,
step: 'confirmation',
})
}
}
getStepProps() {
const { inputValue, walletAddress, step } = this.state
const { inputValue, step, progress, accounts } = this.state
const props = (predicate, props) => (predicate ? props : {})
@ -158,26 +185,52 @@ class AddAccountModal extends PureComponent<Props, State> {
...props(step === 'startWallet', {
wallet: inputValue.wallet,
}),
...props(step === 'confirmation', {
accountName: inputValue.accountName,
onConfirm: this.handleAddAccount,
wallet: inputValue.wallet,
walletAddress,
...props(step === 'inProgress', {
progress,
}),
...props(step === 'listAccounts', {
accounts,
onAddAccount: this.handleAddAccount,
}),
}
}
handleAddAccount = () => {
const { inputValue, walletAddress } = this.state
handleWalletRequest = (e, { data, type }) => {
if (type === 'wallet.getAccounts.progress') {
this.setState({
step: 'inProgress',
progress: data,
})
}
if (type === 'wallet.getAccounts.fail') {
this._timeout = setTimeout(() => this.getWalletInfos(), 1e3)
}
if (type === 'wallet.getAccounts.success') {
this.setState({
accounts: data,
step: 'listAccounts',
})
}
}
handleAddAccount = index => () => {
const { inputValue, accounts } = this.state
const { addAccount, closeModal } = this.props
const account = {
const { id, balance, transactions } = accounts[index]
addAccount({
id,
name: inputValue.accountName,
type: inputValue.wallet,
address: walletAddress,
}
data: {
balance,
transactions,
},
})
addAccount(account)
closeModal('add-account')
}

123
src/helpers/btc.js

@ -1,15 +1,42 @@
import blockexplorer from 'blockchain.info/blockexplorer'
// @flow
const explorer = blockexplorer.usingNetwork(3)
import axios from 'axios'
import bitcoin from 'bitcoinjs-lib'
import { formatCurrencyUnit } from 'ledger-wallet-common/lib/data/currency'
function computeTransaction(address) {
return transaction => {
const outputVal = transaction.out
.filter(o => o.addr === address)
export function format(v: string | number, options: Object = { alwaysShowSign: true }) {
return formatCurrencyUnit(
{
name: 'bitcoin',
code: 'BTC',
symbol: 'b',
magnitude: 8,
},
Number(v),
options.alwaysShowSign,
true,
)
}
export const networks = [
{
...bitcoin.networks.bitcoin,
family: 1,
},
{
...bitcoin.networks.testnet,
family: 1,
},
]
export function computeTransaction(addresses: Array<*>) {
return (transaction: Object) => {
const outputVal = transaction.outputs
.filter(o => addresses.includes(o.address))
.reduce((acc, cur) => acc + cur.value, 0)
const inputVal = transaction.inputs
.filter(i => i.prev_out.addr === address)
.reduce((acc, cur) => acc + cur.prev_out.value, 0)
.filter(i => addresses.includes(i.address))
.reduce((acc, cur) => acc + cur.value, 0)
const balance = outputVal - inputVal
return {
...transaction,
@ -18,12 +45,78 @@ function computeTransaction(address) {
}
}
export async function getAddressData(address) {
const addressData = await explorer.getAddress(address)
const unifiedData = {
address,
balance: addressData.final_balance,
transactions: addressData.txs.map(computeTransaction(address)),
export function getTransactions(addresses: Array<string>) {
return axios.get(
`http://api.ledgerwallet.com/blockchain/v2/btc_testnet/addresses/${addresses.join(
',',
)}/transactions?noToken=true`,
)
}
export async function getAccount({
currentIndex = 0,
hdnode,
segwit,
network,
}: {
currentIndex?: number,
hdnode: Object,
segwit: boolean,
network: Object,
}) {
const script = segwit ? parseInt(network.scriptHash, 10) : parseInt(network.pubKeyHash, 10)
let transactions = []
const pubKeyToSegwitAddress = (pubKey, scriptVersion) => {
const script = [0x00, 0x14].concat(Array.from(bitcoin.crypto.hash160(pubKey)))
const hash160 = bitcoin.crypto.hash160(new Uint8Array(script))
return bitcoin.address.toBase58Check(hash160, scriptVersion)
}
const getPublicAddress = ({ hdnode, path, script, segwit }) => {
hdnode = hdnode.derivePath(path)
if (!segwit) {
return hdnode.getAddress().toString()
}
return pubKeyToSegwitAddress(hdnode.getPublicKeyBuffer(), script)
}
return unifiedData
const nextPath = (index = 0) => {
const count = 20
const getAddress = path => getPublicAddress({ hdnode, path, script, segwit })
return Promise.all(
Array.from(Array(count).keys()).map(v =>
Promise.all([
getAddress(`0/${v + index}`), // external chain
getAddress(`1/${v + index}`), // internal chain
]),
),
).then(async results => {
const currentAddresses = results.reduce((result, v) => [...result, ...v], [])
const { data: { txs } } = await getTransactions(currentAddresses)
transactions = [...transactions, ...txs.map(computeTransaction(currentAddresses))]
if (txs.length > 0) {
return nextPath(index + (count - 1))
}
return {
balance: transactions.reduce((result, v) => {
result += v.balance
return result
}, 0),
transactions,
}
})
}
return nextPath(currentIndex)
}
export function getHDNode({ xpub58, network }: { xpub58: string, network: Object }) {
return bitcoin.HDNode.fromBase58(xpub58, network)
}

1
src/internals/accounts/index.js

@ -0,0 +1 @@
export sync from './sync'

26
src/internals/accounts/sync.js

@ -0,0 +1,26 @@
// @flow
import { getAccount, getHDNode, networks } from 'helpers/btc'
export default (send: Function) => ({
all: async ({ accounts }: { accounts: Array<Object> }) => {
const network = networks[1]
send('accounts.sync.progress', null, { kill: false })
const syncAccount = ({ id }) => {
const hdnode = getHDNode({ xpub58: id, network })
return getAccount({ hdnode, network, segwit: true }).then(account => ({
id,
...account,
}))
}
try {
const result = await Promise.all(accounts.map(syncAccount))
send('accounts.sync.success', result)
} catch (err) {
send('accounts.sync.fail', err.stack || err)
}
},
})

29
src/internals/index.js

@ -0,0 +1,29 @@
// @flow
import objectPath from 'object-path'
process.title = `ledger-wallet-desktop-${process.env.FORK_TYPE}`
process.setMaxListeners(Infinity)
function sendEvent(type: string, data: any, options: Object = { kill: true }) {
process.send({ type, data, options })
}
// $FlowFixMe
const func = require(`./${process.env.FORK_TYPE}`) // eslint-disable-line import/no-dynamic-require
const handlers = Object.keys(func).reduce((result, key) => {
result[key] = func[key](sendEvent)
return result
}, {})
process.on('message', payload => {
const { type, data } = payload
const handler = objectPath.get(handlers, type)
if (!handler) {
return
}
handler(data)
})

28
src/internals/usb/index.js

@ -1,26 +1,2 @@
// @flow
import objectPath from 'object-path'
import devices from './devices'
import wallet from './wallet'
process.title = 'ledger-wallet-desktop-usb'
function sendEvent(type: string, data: any, options: Object = { kill: true }) {
process.send({ type, data, options })
}
const handlers = {
devices: devices(sendEvent),
wallet: wallet(sendEvent),
}
process.on('message', payload => {
const { type, data } = payload
const handler = objectPath.get(handlers, type)
if (!handler) {
return
}
handler(data)
})
export devices from './devices'
export wallet from './wallet'

27
src/internals/usb/wallet.js

@ -1,27 +0,0 @@
// @flow
import CommNodeHid from '@ledgerhq/hw-transport-node-hid'
import Btc from '@ledgerhq/hw-app-btc'
async function getWalletInfos(path, wallet) {
if (wallet === 'btc') {
const comm = await CommNodeHid.open(path)
const btc = new Btc(comm)
const walletInfos = await btc.getWalletPublicKey(`44'/0'/0'/0`)
return walletInfos
}
throw new Error('invalid wallet')
}
export default (sendEvent: Function) => ({
infos: {
request: async ({ path, wallet }: { path: string, wallet: string }) => {
try {
const data = await getWalletInfos(path, wallet)
sendEvent('wallet.infos.success', { path, wallet, data })
} catch (err) {
sendEvent('wallet.infos.fail', { path, wallet, err: err.stack || err })
}
},
},
})

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

@ -0,0 +1,142 @@
// @flow
/* eslint-disable no-bitwise */
import bitcoin from 'bitcoinjs-lib'
import bs58check from 'bs58check'
import Btc from '@ledgerhq/hw-app-btc'
import { getAccount, getHDNode, networks } from 'helpers/btc'
type Coin = 0 | 1
function getCompressPublicKey(publicKey) {
let compressedKeyIndex
if (parseInt(publicKey.substring(128, 130), 16) % 2 !== 0) {
compressedKeyIndex = '03'
} else {
compressedKeyIndex = '02'
}
const result = compressedKeyIndex + publicKey.substring(2, 66)
return result
}
function parseHexString(str: any) {
const result = []
while (str.length >= 2) {
result.push(parseInt(str.substring(0, 2), 16))
str = str.substring(2, str.length)
}
return result
}
function createXpub({ depth, fingerprint, childnum, chainCode, publicKey, network }) {
return [
network.toString(16).padStart(8, '0'),
depth.toString(16).padStart(2, '0'),
fingerprint.toString(16).padStart(8, '0'),
childnum.toString(16).padStart(8, '0'),
chainCode,
publicKey,
].join('')
}
function encodeBase58Check(vchIn) {
vchIn = parseHexString(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}'` : ''}`
}
export default async ({
transport,
currentAccounts,
onProgress,
coin = 1,
segwit = true,
}: {
transport: Object,
currentAccounts: Array<*>,
onProgress: Function,
coin?: Coin,
segwit?: boolean,
}) => {
const btc = new Btc(transport)
const network = networks[coin]
const [p2pkh, p2sh, fam] = [network.pubKeyHash, network.scriptHash, network.family].map(v =>
v.toString(16).padStart(4, 0),
)
await transport.exchange(`e014000005${p2pkh}${p2sh}${fam.substr(-2)}`, [0x9000])
const getPublicKey = path => btc.getWalletPublicKey(path)
let result = bitcoin.crypto.sha256(
await getPublicKey(getPath({ segwit, coin })).then(
({ publicKey }) => new Uint8Array(parseHexString(getCompressPublicKey(publicKey))),
),
)
result = bitcoin.crypto.ripemd160(result)
onProgress(null)
const fingerprint = ((result[0] << 24) | (result[1] << 16) | (result[2] << 8) | result[3]) >>> 0
const getXpub58ByAccount = async ({ account, network }) => {
const { publicKey, chainCode } = await getPublicKey(getPath({ segwit, coin, account }))
const compressPublicKey = getCompressPublicKey(publicKey)
const childnum = (0x80000000 | account) >>> 0
const xpub = createXpub({
depth: 3,
fingerprint,
childnum,
chainCode,
publicKey: compressPublicKey,
network: network.bip32.public,
})
return encodeBase58Check(xpub)
}
const getAllAccounts = async (currentAccount = 0, accounts = {}) => {
const xpub58 = await getXpub58ByAccount({ account: currentAccount, network })
if (currentAccounts.includes(xpub58)) {
return getAllAccounts(currentAccount + 1, accounts) // Skip existing account
}
const hdnode = getHDNode({ xpub58, network })
const { transactions, balance } = await getAccount({ hdnode, network, segwit })
onProgress({
account: currentAccount,
transactions: transactions.length,
})
const hasTransactions = transactions.length > 0
// If the first account is empty we still add it
if (currentAccount === 0 || hasTransactions) {
accounts[currentAccount] = {
id: xpub58,
balance,
transactions,
}
}
if (hasTransactions) {
return getAllAccounts(currentAccount + 1, accounts)
}
return accounts
}
return getAllAccounts()
}

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

@ -0,0 +1,39 @@
// @flow
import CommNodeHid from '@ledgerhq/hw-transport-node-hid'
import getAllAccounts from './accounts'
async function getAllAccountsByWallet({ path, wallet, currentAccounts, onProgress }) {
const transport = await CommNodeHid.open(path)
if (wallet === 'btc') {
return getAllAccounts({ transport, currentAccounts, onProgress })
}
throw new Error('invalid wallet')
}
export default (sendEvent: Function) => ({
getAccounts: async ({
path,
wallet,
currentAccounts,
}: {
path: string,
wallet: string,
currentAccounts: Array<*>,
}) => {
try {
const data = await getAllAccountsByWallet({
path,
wallet,
currentAccounts,
onProgress: progress => sendEvent('wallet.getAccounts.progress', progress, { kill: false }),
})
sendEvent('wallet.getAccounts.success', data)
} catch (err) {
sendEvent('wallet.getAccounts.fail', err.stack || err)
}
},
})

8
src/main/app.js

@ -29,14 +29,10 @@ function createMainWindow() {
window.loadURL(url)
window.on('closed', () => {
window.on('close', () => {
mainWindow = null
})
ipcMain.on('renderer-ready', () => {
window.show()
})
window.webContents.on('devtools-opened', () => {
window.focus()
setImmediate(() => {
@ -76,4 +72,6 @@ app.on('ready', async () => {
}
mainWindow = createMainWindow()
ipcMain.on('renderer-ready', () => mainWindow && mainWindow.show())
})

14
src/main/bridge.js

@ -7,11 +7,15 @@ import { resolve } from 'path'
import setupAutoUpdater from './autoUpdate'
function onChannelUsb(callType) {
function onForkChannel(forkType, callType) {
return (event: any, payload) => {
const { type, data } = payload
const compute = fork(resolve(__dirname, `${__DEV__ ? '../../' : './'}dist/internals/usb`))
const compute = fork(resolve(__dirname, `${__DEV__ ? '../../' : './'}dist/internals`), [], {
env: {
FORK_TYPE: forkType,
},
})
compute.send({ type, data })
compute.on('message', payload => {
@ -31,9 +35,9 @@ function onChannelUsb(callType) {
}
}
// Forwards every usb message to usb process
ipcMain.on('usb', onChannelUsb('async'))
ipcMain.on('usb:sync', onChannelUsb('sync'))
// Forwards every `type` messages to another process
ipcMain.on('usb', onForkChannel('usb', 'async'))
ipcMain.on('accounts', onForkChannel('accounts', 'async'))
const handlers = {
updater: {

30
src/reducers/accounts.js

@ -1,8 +1,8 @@
// @flow
import { handleActions } from 'redux-actions'
import shortid from 'shortid'
import get from 'lodash/get'
import reduce from 'lodash/reduce'
import type { State } from 'reducers'
import type { Account, Accounts, AccountData } from 'types/common'
@ -12,17 +12,12 @@ export type AccountsState = Accounts
const state: AccountsState = {}
const handlers: Object = {
ADD_ACCOUNT: (state: AccountsState, { payload: account }: { payload: Account }) => {
const id = shortid.generate()
return {
...state,
[id]: {
id,
...account,
},
}
},
ADD_ACCOUNT: (state: AccountsState, { payload: account }: { payload: Account }) => ({
...state,
[account.id]: {
...account,
},
}),
FETCH_ACCOUNTS: (state: AccountsState, { payload: accounts }: { payload: Accounts }) => accounts,
SET_ACCOUNT_DATA: (
state: AccountsState,
@ -38,6 +33,17 @@ const handlers: Object = {
// Selectors
export function getTotalBalance(state: { accounts: AccountsState }) {
return reduce(
state.accounts,
(result, account) => {
result += account.data.balance
return result
},
0,
)
}
export function getAccounts(state: { accounts: AccountsState }) {
return state.accounts
}

23
src/renderer/events.js

@ -4,6 +4,7 @@ import { ipcRenderer } from 'electron'
import objectPath from 'object-path'
import { updateDevices, addDevice, removeDevice } from 'actions/devices'
import { syncAccount } from 'actions/accounts'
import { setUpdateStatus } from 'reducers/update'
type MsgPayload = {
@ -13,6 +14,7 @@ type MsgPayload = {
// wait a bit before launching update check
const CHECK_UPDATE_TIMEOUT = 3e3
const SYNC_ACCOUNT_TIMEOUT = 1e3
export function sendEvent(channel: string, msgType: string, data: any) {
ipcRenderer.send(channel, {
@ -28,8 +30,24 @@ export function sendSyncEvent(channel: string, msgType: string, data: any): any
})
}
function syncAccounts(accounts) {
sendEvent('accounts', 'sync.all', {
accounts: Object.entries(accounts).map(([id]: [string, any]) => ({
id,
})),
})
}
export default (store: Object) => {
const handlers = {
accounts: {
sync: {
success: accounts => {
accounts.forEach(account => store.dispatch(syncAccount(account)))
setTimeout(() => syncAccounts(store.getState().accounts), SYNC_ACCOUNT_TIMEOUT)
},
},
},
devices: {
update: devices => {
store.dispatch(updateDevices(devices))
@ -58,12 +76,17 @@ export default (store: Object) => {
handler(data)
})
const state = store.getState()
// First time, we get all devices
sendEvent('usb', 'devices.all')
// Start detection when we plug/unplug devices
sendEvent('usb', 'devices.listen')
// Start accounts sync
syncAccounts(state.accounts)
if (__PROD__) {
// Start check of eventual updates
setTimeout(() => sendEvent('msg', 'updater.init'), CHECK_UPDATE_TIMEOUT)

7
src/renderer/index.js

@ -9,7 +9,7 @@ import createHistory from 'history/createHashHistory'
import createStore from 'renderer/createStore'
import events from 'renderer/events'
import { fetchAccounts, syncAccounts } from 'actions/accounts'
import { fetchAccounts } from 'actions/accounts'
import { fetchSettings } from 'actions/settings'
import { isLocked } from 'reducers/application'
@ -26,17 +26,16 @@ const history = createHistory()
const store = createStore(history)
const rootNode = document.getElementById('app')
events(store)
store.dispatch(fetchSettings())
const state = store.getState() || {}
if (!isLocked(state)) {
store.dispatch(fetchAccounts())
store.dispatch(syncAccounts())
}
events(store)
function r(Comp) {
if (rootNode) {
render(<AppContainer>{Comp}</AppContainer>, rootNode)

2
src/types/common.js

@ -18,7 +18,6 @@ export type Transaction = {
// -------------------- Accounts
export type AccountData = {
address: string,
balance: number,
transactions: Array<Transaction>,
}
@ -27,7 +26,6 @@ export type Account = {
id: string,
name: string,
type: string,
address: string,
data?: AccountData,
}

5
webpack/internals.config.js

@ -20,7 +20,10 @@ module.exports = webpackMain().then(config => ({
devtool: config.devtool,
target: config.target,
entry: dirs(path.resolve(__dirname, '../src/internals')),
entry: {
...dirs(path.resolve(__dirname, '../src/internals')),
index: path.resolve(__dirname, '../src/internals/index'),
},
resolve: {
extensions: config.resolve.extensions,

273
yarn.lock

@ -104,9 +104,9 @@
events "^1.1.1"
invariant "^2.2.0"
"@storybook/addon-actions@^3.3.9":
version "3.3.9"
resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-3.3.9.tgz#2b191548928467fe1dd26dcba606feafbf182d36"
"@storybook/addon-actions@^3.3.10":
version "3.3.10"
resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-3.3.10.tgz#f3e4b538d8260364c55a3ba1e301a2fab9d8d3f2"
dependencies:
deep-equal "^1.0.1"
global "^4.3.2"
@ -115,9 +115,9 @@
react-inspector "^2.2.2"
uuid "^3.1.0"
"@storybook/addon-knobs@^3.3.9":
version "3.3.9"
resolved "https://registry.yarnpkg.com/@storybook/addon-knobs/-/addon-knobs-3.3.9.tgz#855575557868a97c00ce8de8e2e6960f1580f21c"
"@storybook/addon-knobs@^3.3.10":
version "3.3.10"
resolved "https://registry.yarnpkg.com/@storybook/addon-knobs/-/addon-knobs-3.3.10.tgz#25f32cd3d32a1667dc9d8ae484ddfd6223fcffef"
dependencies:
babel-runtime "^6.26.0"
deep-equal "^1.0.1"
@ -131,41 +131,41 @@
react-textarea-autosize "^5.2.1"
util-deprecate "^1.0.2"
"@storybook/addon-links@^3.3.9":
version "3.3.9"
resolved "https://registry.yarnpkg.com/@storybook/addon-links/-/addon-links-3.3.9.tgz#13781ac1c21ddfe347ece6ceab8518c8d1f98a0f"
"@storybook/addon-links@^3.3.10":
version "3.3.10"
resolved "https://registry.yarnpkg.com/@storybook/addon-links/-/addon-links-3.3.10.tgz#4e6c1a0b0bf5b18101bc5001b858b33202ae8209"
dependencies:
"@storybook/components" "^3.3.9"
"@storybook/components" "^3.3.10"
global "^4.3.2"
prop-types "^15.5.10"
"@storybook/addon-options@^3.3.9":
version "3.3.9"
resolved "https://registry.yarnpkg.com/@storybook/addon-options/-/addon-options-3.3.9.tgz#8dca85ae5c6713ca13bead0e8c39b23c7e2138c0"
"@storybook/addon-options@^3.3.10":
version "3.3.10"
resolved "https://registry.yarnpkg.com/@storybook/addon-options/-/addon-options-3.3.10.tgz#536796b6223616a4a8b3c2851c7efbe66036bc8a"
"@storybook/addons@^3.3.9":
version "3.3.9"
resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-3.3.9.tgz#356ce7f1de892d88ca4bc5f686d06e07dd8c2108"
"@storybook/addons@^3.3.10":
version "3.3.10"
resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-3.3.10.tgz#8753007d872013d2376ba71b14396eef3159673b"
"@storybook/channel-postmessage@^3.3.9":
version "3.3.9"
resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-3.3.9.tgz#a59220f9ecbdbe05deac6ac4339715aa587d41dd"
"@storybook/channel-postmessage@^3.3.10":
version "3.3.10"
resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-3.3.10.tgz#4f22b5a665d3c95eb61cf41bbb06872009ace7b5"
dependencies:
"@storybook/channels" "^3.3.9"
"@storybook/channels" "^3.3.10"
global "^4.3.2"
json-stringify-safe "^5.0.1"
"@storybook/channels@^3.3.9":
version "3.3.9"
resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-3.3.9.tgz#3116a6c5e441fd057558870b254c34fe3a9fbfb0"
"@storybook/channels@^3.3.10":
version "3.3.10"
resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-3.3.10.tgz#0b15d47c2ea0cb1c7b735955d74e9d3ca99cdc42"
"@storybook/client-logger@^3.3.9":
version "3.3.9"
resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-3.3.9.tgz#a73e382c383c1bfa6d2ff7fa5cae77cd09efa524"
"@storybook/client-logger@^3.3.10":
version "3.3.10"
resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-3.3.10.tgz#6f8b85c3dfad229794fee88f930df59b163ee144"
"@storybook/components@^3.3.9":
version "3.3.9"
resolved "https://registry.yarnpkg.com/@storybook/components/-/components-3.3.9.tgz#1f7ced8b10a0e405c1d3fd6fe7ef7b8957ddf89f"
"@storybook/components@^3.3.10":
version "3.3.10"
resolved "https://registry.yarnpkg.com/@storybook/components/-/components-3.3.10.tgz#f213a129ed49de33cdaf116da2c2b662b8eb3ea0"
dependencies:
glamor "^2.20.40"
glamorous "^4.11.2"
@ -179,9 +179,9 @@
"@storybook/react-simple-di" "^1.2.1"
babel-runtime "6.x.x"
"@storybook/node-logger@^3.3.9":
version "3.3.9"
resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-3.3.9.tgz#c070ef5ced91b1b1aa7bb3e402855db277ed426b"
"@storybook/node-logger@^3.3.10":
version "3.3.10"
resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-3.3.10.tgz#d9c09a622713ec4726cdd292e798aa98c0503c15"
dependencies:
chalk "^2.3.0"
npmlog "^4.1.2"
@ -211,17 +211,17 @@
dependencies:
babel-runtime "^6.5.0"
"@storybook/react@^3.3.9":
version "3.3.9"
resolved "https://registry.yarnpkg.com/@storybook/react/-/react-3.3.9.tgz#2bd203a5b3c5e5fad4a756ca41d78e62cd49b160"
dependencies:
"@storybook/addon-actions" "^3.3.9"
"@storybook/addon-links" "^3.3.9"
"@storybook/addons" "^3.3.9"
"@storybook/channel-postmessage" "^3.3.9"
"@storybook/client-logger" "^3.3.9"
"@storybook/node-logger" "^3.3.9"
"@storybook/ui" "^3.3.9"
"@storybook/react@^3.3.10":
version "3.3.10"
resolved "https://registry.yarnpkg.com/@storybook/react/-/react-3.3.10.tgz#a55f8f804f3f01d76f1b7e8675e818ee4c107324"
dependencies:
"@storybook/addon-actions" "^3.3.10"
"@storybook/addon-links" "^3.3.10"
"@storybook/addons" "^3.3.10"
"@storybook/channel-postmessage" "^3.3.10"
"@storybook/client-logger" "^3.3.10"
"@storybook/node-logger" "^3.3.10"
"@storybook/ui" "^3.3.10"
airbnb-js-shims "^1.4.0"
autoprefixer "^7.2.3"
babel-loader "^7.1.2"
@ -273,11 +273,11 @@
webpack-dev-middleware "^1.12.2"
webpack-hot-middleware "^2.21.0"
"@storybook/ui@^3.3.9":
version "3.3.9"
resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-3.3.9.tgz#abb1df557131b174bf3c0879863a309ee85de8e3"
"@storybook/ui@^3.3.10":
version "3.3.10"
resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-3.3.10.tgz#99a83b988b01cde1df61b87a58227a50ed196dd1"
dependencies:
"@storybook/components" "^3.3.9"
"@storybook/components" "^3.3.10"
"@storybook/mantra-core" "^1.7.2"
"@storybook/react-komposer" "^2.0.3"
babel-runtime "^6.26.0"
@ -682,6 +682,13 @@ aws4@^1.2.1, aws4@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
axios@^0.17.1:
version "0.17.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.17.1.tgz#2d8e3e5d0bdbd7327f91bc814f5c57660f81824d"
dependencies:
follow-redirects "^1.2.5"
is-buffer "^1.1.5"
axobject-query@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-0.1.0.tgz#62f59dbc59c9f9242759ca349960e7a2fe3c36c0"
@ -1671,6 +1678,12 @@ balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
base-x@^3.0.2:
version "3.0.4"
resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.4.tgz#94c1788736da065edb1d68808869e357c977fa77"
dependencies:
safe-buffer "^5.0.1"
base64-js@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.0.tgz#a39992d723584811982be5e290bb6a53d86700f1"
@ -1705,10 +1718,18 @@ bcryptjs@^2.4.3:
version "2.4.3"
resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb"
bech32@^1.1.2:
version "1.1.3"
resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.3.tgz#bd47a8986bbb3eec34a56a097a84b8d3e9a2dfcd"
big.js@^3.1.3:
version "3.2.0"
resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e"
bigi@^1.1.0, bigi@^1.4.0:
version "1.4.2"
resolved "https://registry.yarnpkg.com/bigi/-/bigi-1.4.2.tgz#9c665a95f88b8b08fc05cfd731f561859d725825"
binary-extensions@^1.0.0:
version "1.11.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.11.0.tgz#46aa1751fb6a2f93ee5e689bb1087d4b14c6c205"
@ -1724,6 +1745,36 @@ bindings@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.0.tgz#b346f6ecf6a95f5a815c5839fc7cdb22502f1ed7"
bip66@^1.1.0:
version "1.1.5"
resolved "https://registry.yarnpkg.com/bip66/-/bip66-1.1.5.tgz#01fa8748785ca70955d5011217d1b3139969ca22"
dependencies:
safe-buffer "^5.0.1"
bitcoin-ops@^1.3.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/bitcoin-ops/-/bitcoin-ops-1.4.0.tgz#90d9c95c067e9a34a6ccaa3dd324ffa84e31e3d8"
bitcoinjs-lib@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/bitcoinjs-lib/-/bitcoinjs-lib-3.3.2.tgz#780c9c53ecb1222adb463b58bef26386067b609a"
dependencies:
bech32 "^1.1.2"
bigi "^1.4.0"
bip66 "^1.1.0"
bitcoin-ops "^1.3.0"
bs58check "^2.0.0"
create-hash "^1.1.0"
create-hmac "^1.1.3"
ecurve "^1.0.0"
merkle-lib "^2.0.10"
pushdata-bitcoin "^1.0.1"
randombytes "^2.0.1"
safe-buffer "^5.0.1"
typeforce "^1.11.3"
varuint-bitcoin "^1.0.4"
wif "^2.0.1"
bl@^1.0.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.1.tgz#cac328f7bee45730d404b692203fcb590e172d5e"
@ -1736,28 +1787,12 @@ block-stream@*:
dependencies:
inherits "~2.0.0"
blockchain.info@^2.11.0:
version "2.11.0"
resolved "https://registry.yarnpkg.com/blockchain.info/-/blockchain.info-2.11.0.tgz#63b46617e194164d377e183e6c667d3ef38ad5b6"
dependencies:
q "^1.4.1"
request-promise "^0.4.3"
url-join "0.0.1"
url-parse "^1.0.5"
url-pattern "^0.10.2"
optionalDependencies:
ws "^1.1.2"
bluebird-lst@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/bluebird-lst/-/bluebird-lst-1.0.5.tgz#bebc83026b7e92a72871a3dc599e219cbfb002a9"
dependencies:
bluebird "^3.5.1"
bluebird@^2.3:
version "2.11.0"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1"
bluebird@^3.4.7, bluebird@^3.5.0, bluebird@^3.5.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9"
@ -1935,6 +1970,19 @@ browserslist@^2.1.2, browserslist@^2.11.1:
caniuse-lite "^1.0.30000792"
electron-to-chromium "^1.3.30"
bs58@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a"
dependencies:
base-x "^3.0.2"
bs58check@<3.0.0, bs58check@^2.0.0, bs58check@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/bs58check/-/bs58check-2.1.1.tgz#8a5d0e587af97b784bf9cbf1b29f454d82bc0222"
dependencies:
bs58 "^4.0.0"
create-hash "^1.1.0"
buffer-indexof@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c"
@ -2131,7 +2179,7 @@ chalk@0.5.1:
strip-ansi "^0.3.0"
supports-color "^0.2.0"
chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3:
chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
dependencies:
@ -2583,7 +2631,7 @@ create-hash@^1.1.0, create-hash@^1.1.2:
ripemd160 "^2.0.0"
sha.js "^2.4.0"
create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4:
create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.3, create-hmac@^1.1.4:
version "1.1.6"
resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.6.tgz#acb9e221a4e17bdb076e90657c42b93e3726cf06"
dependencies:
@ -2979,7 +3027,7 @@ doctrine@1.5.0:
esutils "^2.0.2"
isarray "^1.0.0"
doctrine@^2.0.0, doctrine@^2.0.2:
doctrine@^2.0.0, doctrine@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d"
dependencies:
@ -3080,6 +3128,13 @@ ecc-jsbn@~0.1.1:
dependencies:
jsbn "~0.1.0"
ecurve@^1.0.0:
version "1.0.6"
resolved "https://registry.yarnpkg.com/ecurve/-/ecurve-1.0.6.tgz#dfdabbb7149f8d8b78816be5a7d5b83fcf6de797"
dependencies:
bigi "^1.1.0"
safe-buffer "^5.0.1"
ee-first@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
@ -3556,9 +3611,9 @@ eslint-visitor-keys@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d"
eslint@^4.13.1:
version "4.15.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.15.0.tgz#89ab38c12713eec3d13afac14e4a89e75ef08145"
eslint@^4.16.0:
version "4.16.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.16.0.tgz#934ada9e98715e1d7bbfd6f6f0519ed2fab35cc1"
dependencies:
ajv "^5.3.0"
babel-code-frame "^6.22.0"
@ -3566,7 +3621,7 @@ eslint@^4.13.1:
concat-stream "^1.6.0"
cross-spawn "^5.1.0"
debug "^3.1.0"
doctrine "^2.0.2"
doctrine "^2.1.0"
eslint-scope "^3.7.1"
eslint-visitor-keys "^1.0.0"
espree "^3.5.2"
@ -4024,6 +4079,12 @@ flush-write-stream@^1.0.0:
inherits "^2.0.1"
readable-stream "^2.0.4"
follow-redirects@^1.2.5:
version "1.4.0"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.4.0.tgz#a146a3a5d402201c7a3e6128643f0e336d212b10"
dependencies:
debug "^3.1.0"
for-in@^1.0.1, for-in@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
@ -5604,7 +5665,7 @@ lodash.uniq@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
lodash@^3.10.0, lodash@^3.10.1:
lodash@^3.10.1:
version "3.10.1"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
@ -5776,6 +5837,10 @@ merge-descriptors@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
merkle-lib@^2.0.10:
version "2.0.10"
resolved "https://registry.yarnpkg.com/merkle-lib/-/merkle-lib-2.0.10.tgz#82b8dbae75e27a7785388b73f9d7725d0f6f3326"
methods@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
@ -6301,10 +6366,6 @@ optionator@^0.8.2:
type-check "~0.3.2"
wordwrap "~1.0.0"
options@>=0.0.5:
version "0.0.6"
resolved "https://registry.yarnpkg.com/options/-/options-0.0.6.tgz#ec22d312806bb53e731773e7cdaefcf1c643128f"
ora@^0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/ora/-/ora-0.2.3.tgz#37527d220adcd53c39b73571d754156d5db657a4"
@ -7068,7 +7129,13 @@ punycode@^1.2.4, punycode@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
q@^1.1.2, q@^1.4.1:
pushdata-bitcoin@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/pushdata-bitcoin/-/pushdata-bitcoin-1.0.1.tgz#15931d3cd967ade52206f523aa7331aef7d43af7"
dependencies:
bitcoin-ops "^1.3.0"
q@^1.1.2:
version "1.5.1"
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
@ -7703,15 +7770,6 @@ repeating@^2.0.0:
dependencies:
is-finite "^1.0.0"
request-promise@^0.4.3:
version "0.4.3"
resolved "https://registry.yarnpkg.com/request-promise/-/request-promise-0.4.3.tgz#3c8ddc82f06f8908d720aede1d6794258e22121c"
dependencies:
bluebird "^2.3"
chalk "^1.1.0"
lodash "^3.10.0"
request "^2.34"
request@2.81.0:
version "2.81.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0"
@ -7739,7 +7797,7 @@ request@2.81.0:
tunnel-agent "^0.6.0"
uuid "^3.0.0"
request@^2.34, request@^2.45.0, request@^2.81.0, request@^2.83.0:
request@^2.45.0, request@^2.81.0, request@^2.83.0:
version "2.83.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356"
dependencies:
@ -8206,6 +8264,12 @@ source-map-support@^0.5.0, source-map-support@^0.5.1:
dependencies:
source-map "^0.6.0"
source-map-support@^0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.2.tgz#1a6297fd5b2e762b39688c7fc91233b60984f0a5"
dependencies:
source-map "^0.6.0"
source-map-url@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3"
@ -8797,6 +8861,10 @@ typedarray@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
typeforce@^1.11.3:
version "1.12.0"
resolved "https://registry.yarnpkg.com/typeforce/-/typeforce-1.12.0.tgz#ca40899919f1466d7819e37be039406beb912a2e"
ua-parser-js@^0.7.9:
version "0.7.17"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac"
@ -8853,10 +8921,6 @@ uid-number@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81"
ultron@1.0.x:
version "1.0.2"
resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa"
union-value@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4"
@ -8954,10 +9018,6 @@ urix@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
url-join@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/url-join/-/url-join-0.0.1.tgz#1db48ad422d3402469a87f7d97bdebfe4fb1e3c8"
url-loader@^0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-0.6.2.tgz#a007a7109620e9d988d14bce677a1decb9a993f7"
@ -8979,17 +9039,13 @@ url-parse@1.0.x:
querystringify "0.0.x"
requires-port "1.0.x"
url-parse@^1.0.5, url-parse@^1.1.8:
url-parse@^1.1.8:
version "1.2.0"
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.2.0.tgz#3a19e8aaa6d023ddd27dcc44cb4fc8f7fec23986"
dependencies:
querystringify "~1.0.0"
requires-port "~1.0.0"
url-pattern@^0.10.2:
version "0.10.2"
resolved "https://registry.yarnpkg.com/url-pattern/-/url-pattern-0.10.2.tgz#e9f07104982b72312db4473dd86a527b580015da"
url-to-options@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9"
@ -9054,6 +9110,12 @@ value-equal@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-0.4.0.tgz#c5bdd2f54ee093c04839d71ce2e4758a6890abc7"
varuint-bitcoin@^1.0.4:
version "1.1.0"
resolved "https://registry.yarnpkg.com/varuint-bitcoin/-/varuint-bitcoin-1.1.0.tgz#7a343f50537607af6a3059312b9782a170894540"
dependencies:
safe-buffer "^5.1.1"
vary@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
@ -9253,6 +9315,12 @@ widest-line@^2.0.0:
dependencies:
string-width "^2.1.1"
wif@^2.0.1:
version "2.0.6"
resolved "https://registry.yarnpkg.com/wif/-/wif-2.0.6.tgz#08d3f52056c66679299726fade0d432ae74b4704"
dependencies:
bs58check "<3.0.0"
window-size@0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d"
@ -9301,13 +9369,6 @@ write@^0.2.1:
dependencies:
mkdirp "^0.5.1"
ws@^1.1.2:
version "1.1.5"
resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.5.tgz#cbd9e6e75e09fc5d2c90015f21f0c40875e0dd51"
dependencies:
options ">=0.0.5"
ultron "1.0.x"
xdg-basedir@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4"

Loading…
Cancel
Save