Browse Source

Merge pull request #142 from loeck/master

Add verifyAddress in ReceiveBox
master
Meriadec Pillet 7 years ago
committed by GitHub
parent
commit
1b6708ac11
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      src/components/AccountPage.js
  2. 188
      src/components/ReceiveBox.js
  3. 1
      src/components/SelectAccount/stories.js
  4. 4
      src/components/base/Box/index.js
  5. 2
      src/components/modals/AddAccount/index.js
  6. 34
      src/components/modals/Receive.js
  7. 30
      src/helpers/btc.js
  8. 23
      src/internals/usb/wallet/accounts.js
  9. 23
      src/internals/usb/wallet/index.js
  10. 1
      src/reducers/accounts.js
  11. 5
      src/types/common.js

2
src/components/AccountPage.js

@ -95,7 +95,7 @@ class AccountPage extends PureComponent<Props> {
<Box style={{ width: 300 }}>
<Card title={t('AccountPage.receive')} flow={3}>
<ReceiveBox address={accountData.address} />
<ReceiveBox path={accountData.path} address={accountData.address} />
</Card>
</Box>
</Box>

188
src/components/ReceiveBox.js

@ -1,19 +1,23 @@
// @flow
import React from 'react'
import React, { PureComponent } from 'react'
import { connect } from 'react-redux'
import styled from 'styled-components'
import { ipcRenderer } from 'electron'
import type { MapStateToProps } from 'react-redux'
import type { Device } from 'types/common'
import { getCurrentDevice } from 'reducers/devices'
import { sendEvent } from 'renderer/events'
import Box from 'components/base/Box'
import QRCode from 'components/base/QRCode'
import Icon from 'components/base/Icon'
import Button from 'components/base/Button'
import CopyToClipboard from 'components/base/CopyToClipboard'
import Text from 'components/base/Text'
import Icon from 'components/base/Icon'
import Print from 'components/base/Print'
type Props = {
amount?: string,
address: string,
}
import QRCode from 'components/base/QRCode'
import Text from 'components/base/Text'
export const AddressBox = styled(Box).attrs({
bg: 'cream',
@ -22,7 +26,7 @@ export const AddressBox = styled(Box).attrs({
border-radius: 3px;
border: 1px solid ${p => p.theme.colors.mouse};
cursor: text;
text-alignitems: center;
text-align: center;
user-select: text;
word-break: break-all;
`
@ -35,7 +39,7 @@ const Action = styled(Box).attrs({
fontSize: 0,
})`
font-weight: bold;
text-alignitems: center;
text-align: center;
cursor: pointer;
text-transform: uppercase;
@ -44,44 +48,130 @@ const Action = styled(Box).attrs({
}
`
const ReceiveBox = ({ amount, address }: Props) => (
<Box flow={3}>
<Box alignItems="center">
<QRCode size={150} data={`bitcoin:${address}${amount ? `?amount=${amount}` : ''}`} />
</Box>
<Box alignItems="center" flow={2}>
<Text fontSize={1}>{'Current address'}</Text>
<AddressBox>{address}</AddressBox>
</Box>
<Box horizontal>
<CopyToClipboard
data={address}
render={copy => (
<Action onClick={copy}>
<Icon name="clone" />
<span>{'Copy'}</span>
</Action>
)}
/>
<Print
data={{ address, amount }}
render={(print, isLoading) => (
<Action onClick={print}>
<Icon name="print" />
<span>{isLoading ? '...' : 'Print'}</span>
const mapStateToProps: MapStateToProps<*, *, *> = state => ({
currentDevice: getCurrentDevice(state),
})
type Props = {
currentDevice: Device | null,
address: string,
amount?: string,
path: string,
}
type State = {
isVerified: null | boolean,
isDisplay: boolean,
}
const defaultState = {
isVerified: null,
isDisplay: false,
}
class ReceiveBox extends PureComponent<Props, State> {
static defaultProps = {
amount: undefined,
}
state = {
...defaultState,
}
componentDidMount() {
ipcRenderer.on('msg', this.handleMsgEvent)
}
componentWillUnmount() {
ipcRenderer.removeListener('msg', this.handleMsgEvent)
this.setState({
...defaultState,
})
}
handleMsgEvent = (e, { type }) => {
if (type === 'wallet.verifyAddress.success') {
this.setState({
isVerified: true,
})
}
if (type === 'wallet.verifyAddress.fail') {
this.setState({
isVerified: false,
})
}
}
handleVerifyAddress = () => {
const { currentDevice, path } = this.props
if (currentDevice !== null) {
sendEvent('usb', 'wallet.verifyAddress', {
pathDevice: currentDevice.path,
path,
})
this.setState({
isDisplay: true,
})
}
}
render() {
const { amount, address } = this.props
const { isVerified, isDisplay } = this.state
if (!isDisplay) {
return (
<Box grow alignItems="center" justifyContent="center">
<Button onClick={this.handleVerifyAddress}>Display address on device</Button>
</Box>
)
}
return (
<Box flow={3}>
<Box>
isVerified:{' '}
{isVerified === null
? 'not yet...'
: isVerified === true ? 'ok!' : '/!\\ contact support'}
</Box>
<Box alignItems="center">
<QRCode size={150} data={`bitcoin:${address}${amount ? `?amount=${amount}` : ''}`} />
</Box>
<Box alignItems="center" flow={2}>
<Text fontSize={1}>{'Current address'}</Text>
<AddressBox>{address}</AddressBox>
</Box>
<Box horizontal>
<CopyToClipboard
data={address}
render={copy => (
<Action onClick={copy}>
<Icon name="clone" />
<span>{'Copy'}</span>
</Action>
)}
/>
<Print
data={{ address, amount }}
render={(print, isLoading) => (
<Action onClick={print}>
<Icon name="print" />
<span>{isLoading ? '...' : 'Print'}</span>
</Action>
)}
/>
<Action>
<Icon name="share-square" />
<span>{'Share'}</span>
</Action>
)}
/>
<Action>
<Icon name="share-square" />
<span>{'Share'}</span>
</Action>
</Box>
</Box>
)
ReceiveBox.defaultProps = {
amount: undefined,
</Box>
</Box>
)
}
}
export default ReceiveBox
export default connect(mapStateToProps, null)(ReceiveBox)

1
src/components/SelectAccount/stories.js

@ -17,6 +17,7 @@ const accounts = [...Array(20)].map(() => ({
address: chance.string(),
balance: chance.floating({ min: 0, max: 20 }),
currentIndex: chance.integer({ min: 0, max: 20 }),
path: '',
transactions: [],
},
}))

4
src/components/base/Box/index.js

@ -56,11 +56,11 @@ const RawCard = styled(Box).attrs({ bg: 'white', p: 3, boxShadow: 0, borderRadiu
export const Card = ({ title, ...props }: { title?: string }) => {
if (title) {
return (
<Box flow={4}>
<Box flow={4} grow>
<Text color="dark" ff="Museo Sans" fontSize={6}>
{title}
</Text>
<RawCard {...props} />
<RawCard grow {...props} />
</Box>
)
}

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

@ -183,7 +183,7 @@ class AddAccountModal extends PureComponent<Props, State> {
}
sendEvent('usb', 'wallet.getAccounts', {
path: currentDevice.path,
pathDevice: currentDevice.path,
wallet: inputValue.wallet,
currentAccounts: accounts.map(acc => acc.id),
})

34
src/components/modals/Receive.js

@ -62,6 +62,7 @@ class ReceiveModal extends PureComponent<Props, State> {
onHide={this.handleHide}
render={({ data, onClose }) => {
const account = this.getAccount(data)
const accountData = get(account, 'data', {})
return (
<ModalBody onClose={onClose} flow={3}>
@ -72,21 +73,24 @@ class ReceiveModal extends PureComponent<Props, State> {
<Label>Account</Label>
<SelectAccount value={account} onChange={this.handleChangeInput('account')} />
</Box>
{account &&
account.data && (
<Fragment>
<Box flow={1}>
<Label>Request amount</Label>
<Input
type="number"
min={0}
max={account.data.balance / 1e8}
onChange={this.handleChangeInput('amount')}
/>
</Box>
<ReceiveBox amount={amount} address={get(account, 'data.address', '')} />
</Fragment>
)}
{accountData && (
<Fragment>
<Box flow={1}>
<Label>Request amount</Label>
<Input
type="number"
min={0}
max={accountData.balance / 1e8}
onChange={this.handleChangeInput('amount')}
/>
</Box>
<ReceiveBox
path={accountData.path}
amount={amount}
address={accountData.address || ''}
/>
</Fragment>
)}
<Box horizontal justifyContent="center">
<Button primary onClick={onClose}>
Close

30
src/helpers/btc.js

@ -37,6 +37,7 @@ export function getTransactions(addresses: Array<string>) {
export async function getAccount({
allAddresses = [],
currentIndex = 0,
path,
hdnode,
segwit,
network,
@ -44,6 +45,7 @@ export async function getAccount({
}: {
allAddresses?: Array<string>,
currentIndex?: number,
path: string,
hdnode: Object,
segwit: boolean,
network: Object,
@ -69,15 +71,12 @@ export async function getAccount({
return pubKeyToSegwitAddress(hdnode.getPublicKeyBuffer(), script)
}
const getPath = (type, index) => `${type === 'external' ? 0 : 1}/${index}`
const getAddress = ({ type, index }) => ({
type,
index,
address: getPublicAddress({
hdnode,
path: `${type === 'external' ? 0 : 1}/${index}`,
script,
segwit,
}),
address: getPublicAddress({ hdnode, path: getPath(type, index), script, segwit }),
})
const getAsyncAddress = params =>
@ -89,7 +88,7 @@ export async function getAccount({
index: 0,
}
return {
index: lastAddress.index,
...lastAddress,
address: getAddress({ type: 'external', index: lastAddress.index + 1 }).address,
}
}
@ -129,19 +128,16 @@ export async function getAccount({
return result
}, 0)
const currentAddress =
lastAddress !== null ? lastAddress : getAddress({ type: 'external', index: 0 })
return {
balance,
address: currentAddress.address,
allAddresses,
balance,
currentIndex: currentAddress.index,
path: `${path}/${getPath('external', currentAddress.index + 1)}`,
transactions,
...(lastAddress !== null
? {
currentIndex: lastAddress.index,
address: lastAddress.address,
}
: {
currentIndex: 0,
address: getAddress({ type: 'external', index: 0 }).address,
}),
}
})

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

@ -51,6 +51,20 @@ function getPath({ coin, account, segwit }: { coin: Coin, account?: any, segwit:
return `${segwit ? 49 : 44}'/${coin}'${account !== undefined ? `/${account}'` : ''}`
}
export function verifyAddress({
transport,
path,
segwit = true,
}: {
transport: Object,
path: string,
segwit?: boolean,
}) {
const btc = new Btc(transport)
return btc.getWalletPublicKey(path, true, segwit)
}
export default async ({
transport,
currentAccounts,
@ -87,8 +101,8 @@ export default async ({
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 getXpub58ByPath = async ({ path, account, network }) => {
const { publicKey, chainCode } = await getPublicKey(path)
const compressPublicKey = getCompressPublicKey(publicKey)
const childnum = (0x80000000 | account) >>> 0
@ -106,14 +120,15 @@ export default async ({
}
const getAllAccounts = async (currentAccount = 0, accounts = []) => {
const xpub58 = await getXpub58ByAccount({ account: currentAccount, network })
const path = getPath({ segwit, coin, account: currentAccount })
const xpub58 = await getXpub58ByPath({ path, account: currentAccount, network })
if (currentAccounts.includes(xpub58)) {
return getAllAccounts(currentAccount + 1, accounts) // Skip existing account
}
const hdnode = getHDNode({ xpub58, network })
const account = await getAccount({ hdnode, network, segwit, asyncDelay: 0 })
const account = await getAccount({ path, hdnode, network, segwit, asyncDelay: 0 })
onProgress({
account: currentAccount,

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

@ -2,10 +2,10 @@
import CommNodeHid from '@ledgerhq/hw-transport-node-hid'
import getAllAccounts from './accounts'
import getAllAccounts, { verifyAddress } from './accounts'
async function getAllAccountsByWallet({ path, wallet, currentAccounts, onProgress }) {
const transport = await CommNodeHid.open(path)
async function getAllAccountsByWallet({ pathDevice, wallet, currentAccounts, onProgress }) {
const transport = await CommNodeHid.open(pathDevice)
if (wallet === 'btc') {
return getAllAccounts({ transport, currentAccounts, onProgress })
@ -16,17 +16,17 @@ async function getAllAccountsByWallet({ path, wallet, currentAccounts, onProgres
export default (sendEvent: Function) => ({
getAccounts: async ({
path,
pathDevice,
wallet,
currentAccounts,
}: {
path: string,
pathDevice: string,
wallet: string,
currentAccounts: Array<*>,
}) => {
try {
const data = await getAllAccountsByWallet({
path,
pathDevice,
wallet,
currentAccounts,
onProgress: progress => sendEvent('wallet.getAccounts.progress', progress, { kill: false }),
@ -37,4 +37,15 @@ export default (sendEvent: Function) => ({
sendEvent('wallet.getAccounts.fail', err.stack || err)
}
},
verifyAddress: async ({ pathDevice, path }: { pathDevice: string, path: string }) => {
const transport = await CommNodeHid.open(pathDevice)
try {
await verifyAddress({ transport, path })
sendEvent('wallet.verifyAddress.success')
} catch (err) {
sendEvent('wallet.verifyAddress.fail')
}
},
})

1
src/reducers/accounts.js

@ -29,6 +29,7 @@ const defaultAccountData: AccountData = {
address: '',
balance: 0,
currentIndex: 0,
path: '',
transactions: [],
}

5
src/types/common.js

@ -23,15 +23,16 @@ export type AccountData = {
address: string,
balance: number,
currentIndex: number,
path: string,
transactions: Array<Transaction>,
}
export type Account = {
id: string,
archived?: boolean,
data?: AccountData,
id: string,
name: string,
type: string,
data?: AccountData,
}
export type Accounts = Array<Account>

Loading…
Cancel
Save