on 05c1914 help sign in
bridge/src/views/Ledger.js
139 lines 4.5 kB history raw
1import * as bip32 from 'bip32'
2import React from 'react'
3import Maybe from 'folktale/maybe'
4import { Button } from '../components/Base'
5import { Row, Col, H1, P, H2 } from '../components/Base'
6import { Input, InnerLabel } from '../components/Base'
7import Transport from '@ledgerhq/hw-transport-u2f'
8import Eth from '@ledgerhq/hw-app-eth'
9import * as secp256k1 from 'secp256k1'
11import { LEDGER_BASE_PATH } from '../lib/ledger'
12import { ROUTE_NAMES } from '../lib/router'
14const chopHdPrefix = str =>
15 str.slice(0, 2) === "m/"
16 ? str.slice(2)
17 : str
19const addHdPrefix = str =>
20 str.slice(0, 2) === "m/"
21 ? str
22 : "m/" + str
24class Ledger extends React.Component {
25 constructor(props) {
26 super(props)
28 this.state = {
29 hdpath: LEDGER_BASE_PATH
30 }
32 this.pollDevice = this.pollDevice.bind(this)
33 this.handleHdPathInput = this.handleHdPathInput.bind(this)
34 }
36 handleHdPathInput(hdpath) {
37 this.setState({ hdpath })
38 }
40 async pollDevice() {
41 const { setWallet, setWalletHdPath } = this.props
42 const { hdpath } = this.state
44 const transport = await Transport.create()
45 const eth = new Eth(transport)
46 const path = chopHdPrefix(hdpath)
48 eth.getAddress(path, false, true)
49 .then(info => {
50 const publicKey = Buffer.from(info.publicKey, 'hex')
51 const chainCode = Buffer.from(info.chainCode, 'hex')
52 const pub = secp256k1.publicKeyConvert(publicKey, true)
53 const hd = bip32.fromPublicKey(pub, chainCode)
54 setWallet(Maybe.Just(hd))
55 setWalletHdPath(addHdPrefix(hdpath))
56 }, _ => {
57 setWallet(Maybe.Nothing())
58 })
59 }
61 render() {
62 const { pushRoute, popRoute, wallet } = this.props
63 const { hdpath } = this.state
65 return (
67 <Row>
68 <Col className={'measure-md'}>
69 <H1>{ 'Authenticate With Your Ledger' }</H1>
71 <H2>{'Running on HTTPS?'}</H2>
73 <P>
74 { `Connect and authenticate to your Ledger, and then open the
75 "Ethereum" application. If you're running on older firmware,
76 make sure the "browser support" option is turned on. To sign
77 transactions, you'll also need to enable the "contract data"
78 option.` }
79 </P>
81 <P>
82 { `If you'd like to use a custom derivation path, you may enter
83 it below.`
84 }
85 </P>
87 <H2>{'Running on HTTP?'}</H2>
89 <P>{`To authenticate and sign transactions with a Ledger, Bridge
90 must be serving over HTTPS on localhost. You can do this via the
91 following:`}</P>
93 <ol className={'measure-md'}>
94 <li className={'mt-4'}>{'Install'} <a target={'_blank'} href={'https://github.com/FiloSottile/mkcert'}>{'mkcert'}</a></li>
95 <li className={'mt-4'}>{'Install a local certificate authority via '}<code>{'mkcert -install'}</code></li>
96 <li className={'mt-4'}>{'In your '}<code>{'bridge'}</code>{' directory, generate a certificate valid for localhost via '} <code>{'mkcert localhost'}</code>{'.'}
97 {'This will produce two files: '}<code>{'localhost.pem'}</code> {', the local certificate, and '}<code>{'localhost-key.pem'}</code>{', its corresponding private key.'}
98 </li>
99 <li className={'mt-4'}>{'Run '}<code>{'python bridge-https.py'}</code></li>
100 </ol>
102 <Input
103 className='pt-8 mt-8 text-mono'
104 prop-size='md'
105 prop-format='innerLabel'
106 name='hdpath'
107 value={ addHdPrefix(hdpath) }
108 autocomplete='off'
109 onChange={ this.handleHdPathInput }>
110 <InnerLabel>{'HD Path'}</InnerLabel>
111 </Input>
113 <Button
114 prop-size={'wide lg'}
115 className={'mt-8'}
116 onClick={ this.pollDevice }>
117 { 'Authenticate →' }
118 </Button>
120 <Button
121 className={'mt-8'}
122 prop-size={'wide lg'}
123 disabled={ Maybe.Nothing.hasInstance(wallet) }
124 onClick={
125 () => {
126 popRoute()
127 pushRoute(ROUTE_NAMES.SHIPS)
128 }
129 }>
130 { 'Continue →' }
131 </Button>
133 </Col>
134 </Row>
135 )
136 }
139export default Ledger