on master help sign in
Merge pull request #13 from urbit/jt-hardware-path

Add alternate HD path derivation for hardware wallets and trezor tx signing

commit98d991649a9f64ffb5045c5bc580dc3db7775219 authorWill Kim <william.jahaziel.kim@gmail.com> dateFri 18 Jan 2019, 20:17 (8 years ago) parent6da95c5360c860
16 files changed+188−284
src/Bridge.js+11−4 src/components/StatelessTransaction.js+1 src/components/Transaction/Signer.js+4−3 src/components/Transaction/StatelessSigner.js−194 src/lib/ledger.js+8−2 src/lib/trezor.js+2−2 src/lib/txn.js+5−1 src/views/AcceptTransfer.js+1 src/views/CancelTransfer.js+1 src/views/CreateGalaxy.js+1 src/views/IssueChild.js+1 src/views/Ledger.js+81−41 src/views/Mnemonic.js+2−1 src/views/SetProxy.js+1 src/views/Transfer.js+1 src/views/Trezor.js+68−36
src/Bridge.js+11−4
@@ -9,7 +9,7 @@ import { Container, Row, Col } from './components/Base'
99
1010import { ROUTE_NAMES, router } from './lib/router'
1111import { NETWORK_NAMES } from './lib/network'
12−import { WALLET_NAMES } from './lib/wallet'
12+import { WALLET_NAMES, DEFAULT_HD_PATH } from './lib/wallet'
1313import { BRIDGE_ERROR } from './lib/error'
1414
1515class Bridge extends React.Component {
@@ -27,6 +27,7 @@ class Bridge extends React.Component {
2727 // wallet
2828 walletType: WALLET_NAMES.MNEMONIC,
2929 wallet: Maybe.Nothing(),
30+ walletHdPath: DEFAULT_HD_PATH,
3031 // urbit wallet-related
3132 urbitWallet: Maybe.Nothing(),
3233 authMnemonic: Maybe.Nothing(),
@@ -44,6 +45,7 @@ class Bridge extends React.Component {
4445 this.setNetwork = this.setNetwork.bind(this)
4546 this.setWalletType = this.setWalletType.bind(this)
4647 this.setWallet = this.setWallet.bind(this)
48+ this.setWalletHdPath = this.setWalletHdPath.bind(this)
4749 this.setUrbitWallet = this.setUrbitWallet.bind(this)
4850 this.setAuthMnemonic = this.setAuthMnemonic.bind(this)
4951 this.setPointCursor = this.setPointCursor.bind(this)
@@ -142,9 +144,11 @@ class Bridge extends React.Component {
142144 }
143145
144146 setWallet(wallet) {
145− this.setState({
146− wallet: wallet
147− })
147+ this.setState({ wallet })
148+ }
149+
150+ setWalletHdPath(walletHdPath) {
151+ this.setState({ walletHdPath })
148152 }
149153
150154 setUrbitWallet(urbitWallet) {
@@ -176,6 +180,7 @@ class Bridge extends React.Component {
176180 routeCrumbs,
177181 networkType,
178182 walletType,
183+ walletHdPath,
179184 wallet,
180185 urbitWallet,
181186 authMnemonic,
@@ -214,8 +219,10 @@ class Bridge extends React.Component {
214219 contracts={ contracts }
215220 // wallet
216221 setWalletType={ this.setWalletType }
222+ setWalletHdPath={ this.setWalletHdPath }
217223 setWallet={ this.setWallet }
218224 walletType={ walletType }
225+ walletHdPath={ walletHdPath }
219226 wallet={ wallet }
220227 // urbit wallet
221228 urbitWallet={ urbitWallet }
src/components/StatelessTransaction.js+1
@@ -113,6 +113,7 @@ const StatelessTransaction = props => {
113113 onClick={ () => signTransaction({
114114 wallet: props.wallet,
115115 walletType: props.walletType,
116+ walletHdPath: props.walletHdPath,
116117 txn: props.txn,
117118 setStx: props.setStx,
118119 nonce: props.nonce,
src/components/Transaction/Signer.js+4−3
@@ -76,7 +76,8 @@ class Signer extends React.Component {
7676 }
7777
7878 async signTransaction(signingArgs) {
79− const { wallet, walletType, txn, setSignedTransaction } = this.props
79+ const { wallet, walletType, txn, walletHdPath } = this.props
80+ const { setSignedTransaction } = this.props
8081
8182 let { nonce, chainId, gasPrice, gasLimit } = signingArgs
8283 nonce = this.toHex(nonce)
@@ -104,9 +105,9 @@ class Signer extends React.Component {
104105 const stx = new Tx(utx)
105106
106107 if (walletType === WALLET_NAMES.LEDGER) {
107− await ledgerSignTransaction(stx)
108+ await ledgerSignTransaction(stx, walletHdPath)
108109 } else if (walletType === WALLET_NAMES.TREZOR) {
109− await trezorSignTransaction(stx)
110+ await trezorSignTransaction(stx, walletHdPath)
110111 } else {
111112 stx.sign(sec)
112113 }
src/components/Transaction/StatelessSigner.jsdeleted−194
@@ -1,194 +0,0 @@
1−import Tx from 'ethereumjs-tx'
2−import React from 'react'
3−import Web3 from 'web3'
4−import { Button } from '../Base'
5−import { Input, InnerLabel } from '../Base'
6−
7−import { ledgerSignTransaction } from '../../lib/ledger'
8−import { WALLET_NAMES, addressFromSecp256k1Public } from '../../lib/wallet'
9−import { BRIDGE_ERROR } from '../../lib/error'
10−
11−class Signer extends React.Component {
12− constructor(props) {
13− super(props)
14−
15− // this.state = {
16− // nonce: '',
17− // gasPrice: '5',
18− // chainId: '',
19− // gasLimit: '600000'
20− // }
21−
22− const dummy = new Web3()
23− this.toHex = dummy.utils.toHex
24− this.toWei = dummy.utils.toWei
25− this.fromWei = dummy.utils.fromWei
26−
27− this.handleNonceInput = this.handleNonceInput.bind(this)
28− this.handleChainIdInput = this.handleChainIdInput.bind(this)
29− this.handleGasPriceInput = this.handleGasPriceInput.bind(this)
30− this.handleGasLimitInput = this.handleGasLimitInput.bind(this)
31− this.signTransaction = this.signTransaction.bind(this)
32− }
33−
34− componentDidMount() {
35− const { web3, wallet } = this.props
36−
37− const addr = wallet.matchWith({
38− Just: (wal) => addressFromSecp256k1Public(wal.value.publicKey),
39− Nothing: () => {
40− throw BRIDGE_ERROR.MISSING_WALLET
41− }
42− })
43−
44− web3.map(w3 =>
45− this.getTxnInfo(w3, addr)
46− .then(txInfo => this.setState(txInfo))
47− )
48− }
49−
50− getTxnInfo = async (web3, addr) => {
51− let nonce = await web3.eth.getTransactionCount(addr)
52− let chainId = await web3.eth.net.getId()
53− let gasPrice = await web3.eth.getGasPrice()
54− return {
55− nonce: nonce,
56− chainId: chainId,
57− gasPrice: this.fromWei(gasPrice, 'gwei')
58− }
59− }
60−
61− handleNonceInput(nonce) {
62− this.setState({ nonce })
63− }
64−
65− handleChainIdInput(chainId) {
66− this.setState({ chainId })
67− }
68−
69− handleGasPriceInput(gasPrice) {
70− this.setState({ gasPrice })
71− }
72−
73− handleGasLimitInput(gasLimit) {
74− this.setState({ gasLimit })
75− }
76−
77− async signTransaction(signingArgs) {
78− const { wallet, walletType, txn, setSignedTransaction } = this.props
79−
80− let { nonce, chainId, gasPrice, gasLimit } = signingArgs
81− nonce = this.toHex(nonce)
82− chainId = this.toHex(chainId)
83− gasPrice = this.toHex(this.toWei(gasPrice, 'gwei'))
84− gasLimit = this.toHex(gasLimit)
85−
86− const wal = wallet.matchWith({
87− Just: (w) => w.value,
88− Nothing: () => {
89− throw BRIDGE_ERROR.MISSING_WALLET
90− }
91− })
92−
93− const sec = wal.privateKey
94−
95− const utx = txn.matchWith({
96− Just: (tx) =>
97− Object.assign(tx.value, { nonce, chainId, gasPrice, gasLimit }),
98− Nothing: () => {
99− throw BRIDGE_ERROR.MISSING_TXN
100− }
101− })
102−
103− const stx = new Tx(utx)
104−
105− if (walletType === WALLET_NAMES.LEDGER) {
106− await ledgerSignTransaction(stx)
107− } else {
108− stx.sign(sec)
109− }
110−
111− setSignedTransaction(stx)
112− }
113−
114− render() {
115−
116− const { state, props } = this
117−
118− const signingArgs = {
119− nonce: props.nonce,
120− gasPrice: props.gasPrice,
121− gasLimit: props.gasLimit,
122− chainId: props.chainId,
123− }
124−
125−
126−
127− return (
128− <React.Fragment>
129− <Input
130− className='mono mt-4'
131− prop-size='md'
132− prop-format='innerLabel'
133− value={ props.gasPrice }
134− onChange={ v => this.handleGasPriceInput(v) }>
135− <InnerLabel>
136− { 'Gas Price (gwei)' }
137− </InnerLabel>
138− </Input>
139−
140− <Input
141− className='mono mt-4'
142− prop-size='md'
143− prop-format='innerLabel'
144− value={ props.gasLimit }
145− onChange={ v => this.handleGasLimitInput(v) }>
146− <InnerLabel>
147− { 'Gas Limit' }
148− </InnerLabel>
149− </Input>
150−
151− {
152− props.web3.matchWith({
153− Just: () => <div />,
154− Nothing: () =>
155− <React.Fragment>
156− <Input
157− className='mono'
158− prop-size='md'
159− prop-format='innerLabel'
160− value={ props.nonce }
161− onChange={ v => this.handleNonceInput(v) }>
162− <InnerLabel>
163− { 'Nonce' }
164− </InnerLabel>
165− </Input>
166−
167− <Input
168− className='mono mt-4 mb-8'
169− prop-size='md'
170− prop-format='innerLabel'
171− value={ props.chainId }
172− onChange={ v => this.handleChainIdInput(v) }>
173− <InnerLabel>
174− { 'Chain ID' }
175− </InnerLabel>
176− </Input>
177−
178− </React.Fragment>
179− })
180− }
181−
182− <Button
183− disabled={ props.disabled }
184− prop-size={'lg wide'}
185− prop-color={this.props['prop-color']}
186− onClick={ () => this.signTransaction(signingArgs) }>
187− { 'Sign Transaction' }
188− </Button>
189− </React.Fragment>
190− )
191− }
192−}
193−
194−export default Signer
src/lib/ledger.js+8−2
@@ -3,12 +3,18 @@ import Eth from '@ledgerhq/hw-app-eth'
33
44const LEDGER_BASE_PATH = "44'/60'/0'/0/0"
55
6−const ledgerSignTransaction = async txn => {
6+const chopHdPrefix = str =>
7+ str.slice(0, 2) === "m/"
8+ ? str.slice(2)
9+ : str
10+
11+const ledgerSignTransaction = async (txn, hdpath) => {
712 const transport = await Transport.create()
813 const eth = new Eth(transport)
14+ const path = chopHdPrefix(hdpath)
915
1016 const serializedTx = txn.serialize().toString('hex')
11− const sig = await eth.signTransaction(LEDGER_BASE_PATH, serializedTx)
17+ const sig = await eth.signTransaction(path, serializedTx)
1218
1319 txn.v = Buffer.from(sig.v, 'hex')
1420 txn.r = Buffer.from(sig.r, 'hex')
src/lib/trezor.js+2−2
@@ -7,7 +7,7 @@ const formatChainId = val =>
77 ? val
88 : parseInt(val.slice(2), 16) // assume 0x-prefixed hex string
99
10−const trezorSignTransaction = async txn => {
10+const trezorSignTransaction = async (txn, hdpath) => {
1111 const trezorFormattedTxn = {
1212 to: txn.to.toString('hex'),
1313 value: txn.value.toString('hex'),
@@ -19,7 +19,7 @@ const trezorSignTransaction = async txn => {
1919 }
2020
2121 const sig = await TrezorConnect.ethereumSignTransaction({
22− path: TREZOR_BASE_PATH,
22+ path: hdpath,
2323 transaction: trezorFormattedTxn
2424 })
2525
src/lib/txn.js+5−1
@@ -6,6 +6,7 @@ import Web3 from 'web3'
66
77import { BRIDGE_ERROR } from '../lib/error'
88import { ledgerSignTransaction } from '../lib/ledger'
9+import { trezorSignTransaction } from '../lib/trezor'
910import {
1011 WALLET_NAMES,
1112 addHexPrefix
@@ -76,6 +77,7 @@ const signTransaction = async config => {
7677 let {
7778 wallet,
7879 walletType,
80+ walletHdPath,
7981 txn,
8082 setStx,
8183 nonce,
@@ -109,7 +111,9 @@ const signTransaction = async config => {
109111 const stx = new Tx(utx)
110112
111113 if (walletType === WALLET_NAMES.LEDGER) {
112− await ledgerSignTransaction(stx)
114+ await ledgerSignTransaction(stx, walletHdPath)
115+ } else if (walletType === WALLET_NAMES.TREZOR) {
116+ await trezorSignTransaction(stx, walletHdPath)
113117 } else {
114118 stx.sign(sec)
115119 }
src/views/AcceptTransfer.js+1
@@ -303,6 +303,7 @@ class AcceptTransfer extends React.Component {
303303 contracts={props.contracts}
304304 wallet={props.wallet}
305305 walletType={props.walletType}
306+ walletHdPath={props.walletHdPath}
306307 // Tx
307308 txn={state.txn}
308309 stx={state.stx}
src/views/CancelTransfer.js+1
@@ -234,6 +234,7 @@ class CancelTransfer extends React.Component {
234234 contracts={props.contracts}
235235 wallet={props.wallet}
236236 walletType={props.walletType}
237+ walletHdPath={props.walletHdPath}
237238 // Tx
238239 txn={state.txn}
239240 stx={state.stx}
src/views/CreateGalaxy.js+1
@@ -250,6 +250,7 @@ class CreateGalaxy extends React.Component {
250250 contracts={props.contracts}
251251 wallet={props.wallet}
252252 walletType={props.walletType}
253+ walletHdPath={props.walletHdPath}
253254 // Tx
254255 txApproval={state.txApproval}
255256 txn={state.txn}
src/views/IssueChild.js+1
@@ -461,6 +461,7 @@ class IssueChild extends React.Component {
461461 contracts={props.contracts}
462462 wallet={props.wallet}
463463 walletType={props.walletType}
464+ walletHdPath={props.walletHdPath}
464465 // Tx
465466 txn={state.txn}
466467 stx={state.stx}
src/views/Ledger.js+81−41
@@ -3,6 +3,7 @@ import React from 'react'
33import Maybe from 'folktale/maybe'
44import { Button } from '../components/Base'
55import { Row, Col, H1, P, H2 } from '../components/Base'
6+import { Form, Input, InnerLabel } from '../components/Base'
67import Transport from '@ledgerhq/hw-transport-u2f'
78import Eth from '@ledgerhq/hw-app-eth'
89import * as secp256k1 from 'secp256k1'
@@ -10,58 +11,78 @@ import * as secp256k1 from 'secp256k1'
1011import { LEDGER_BASE_PATH } from '../lib/ledger'
1112import { ROUTE_NAMES } from '../lib/router'
1213
14+const chopHdPrefix = str =>
15+ str.slice(0, 2) === "m/"
16+ ? str.slice(2)
17+ : str
18+
19+const addHdPrefix = str =>
20+ str.slice(0, 2) === "m/"
21+ ? str
22+ : "m/" + str
23+
1324class Ledger extends React.Component {
25+ constructor(props) {
26+ super(props)
1427
15− componentDidMount() {
16− this.pollDevice()
28+ this.state = {
29+ hdpath: LEDGER_BASE_PATH
30+ }
1731
18− this.pollerId = setInterval(
19− () => this.pollDevice(),
20− 500
21− )
32+ this.pollDevice = this.pollDevice.bind(this)
33+ this.handleHdPathInput = this.handleHdPathInput.bind(this)
2234 }
2335
24− componentWillUnmount() {
25− clearInterval(this.pollerId)
36+ handleHdPathInput(hdpath) {
37+ this.setState({ hdpath })
2638 }
2739
2840 async pollDevice() {
29− const { wallet, setWallet } = this.props
30−
31− if (Maybe.Nothing.hasInstance(wallet)) {
32− const transport = await Transport.create()
33− const eth = new Eth(transport)
34−
35− eth.getAddress(LEDGER_BASE_PATH, false, true)
36− .then(info => {
37− const publicKey = Buffer.from(info.publicKey, 'hex')
38− const chainCode = Buffer.from(info.chainCode, 'hex')
39− const pub = secp256k1.publicKeyConvert(publicKey, true)
40− const hd = bip32.fromPublicKey(pub, chainCode)
41− setWallet(Maybe.Just(hd))
42− }, _ => {
43− setWallet(Maybe.Nothing())
44− })
45− }
41+ const { setWallet, setWalletHdPath } = this.props
42+ const { hdpath } = this.state
43+
44+ const transport = await Transport.create()
45+ const eth = new Eth(transport)
46+ const path = chopHdPrefix(hdpath)
47+
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+ })
4659 }
4760
4861 render() {
4962 const { pushRoute, popRoute, wallet } = this.props
63+ const { hdpath } = this.state
5064
5165 return (
5266
5367 <Row>
54− <Col>
68+ <Col className={'measure-md'}>
5569 <H1>{ 'Authenticate With Your Ledger' }</H1>
5670
5771 <H2>{'Running on HTTPS?'}</H2>
5872
5973 <P>
6074 { `Connect and authenticate to your Ledger, and then open the
61− "Ethereum" application. If you're running on older firmware, make sure the browser
62− support" option is turned on.` }
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.` }
6379 </P>
6480
81+ <P>
82+ { `If you'd like to use a custom derivation path, you may enter
83+ it below.`
84+ }
85+ </P>
6586
6687 <H2>{'Running on HTTP?'}</H2>
6788
@@ -78,19 +99,38 @@ class Ledger extends React.Component {
7899 <li className={'mt-4'}>{'Run '}<code>{'python bridge-https.py'}</code></li>
79100 </ol>
80101
81−
82− <Button
83− className={'mt-8'}
84− disabled={ Maybe.Nothing.hasInstance(wallet) }
85− onClick={
86− () => {
87− popRoute()
88− pushRoute(ROUTE_NAMES.SHIPS)
89− }
90− }
91− >
92− { 'Continue →' }
93− </Button>
102+ <Form>
103+ <Input
104+ className='pt-8 mt-8'
105+ prop-size='md'
106+ prop-format='innerLabel'
107+ name='hdpath'
108+ value={ addHdPrefix(hdpath) }
109+ autocomplete='off'
110+ onChange={ this.handleHdPathInput }>
111+ <InnerLabel>{'HD Path'}</InnerLabel>
112+ </Input>
113+
114+ <Button
115+ prop-size={'wide lg'}
116+ className={'mt-8'}
117+ onClick={ this.pollDevice }>
118+ { 'Authenticate →' }
119+ </Button>
120+
121+ <Button
122+ className={'mt-8'}
123+ prop-size={'wide lg'}
124+ disabled={ Maybe.Nothing.hasInstance(wallet) }
125+ onClick={
126+ () => {
127+ popRoute()
128+ pushRoute(ROUTE_NAMES.SHIPS)
129+ }
130+ }>
131+ { 'Continue →' }
132+ </Button>
133+ </Form>
94134
95135 </Col>
96136 </Row>
src/views/Mnemonic.js+2−1
@@ -59,10 +59,11 @@ class Mnemonic extends React.Component {
5959 }
6060
6161 attemptWalletDerivation(mnemonic, hdpath) {
62− const { setWallet, setAuthMnemonic } = this.props
62+ const { setWallet, setAuthMnemonic, setWalletHdPath } = this.props
6363 const wallet = walletFromMnemonic(mnemonic, hdpath)
6464 setWallet(wallet)
6565 setAuthMnemonic(Maybe.Just(mnemonic))
66+ setWalletHdPath(hdpath)
6667 }
6768
6869 render() {
src/views/SetProxy.js+1
@@ -320,6 +320,7 @@ class SetProxy extends React.Component {
320320 contracts={props.contracts}
321321 wallet={props.wallet}
322322 walletType={props.walletType}
323+ walletHdPath={props.walletHdPath}
323324 // Tx
324325 txn={state.txn}
325326 stx={state.stx}
src/views/Transfer.js+1
@@ -292,6 +292,7 @@ class Transfer extends React.Component {
292292 contracts={props.contracts}
293293 wallet={props.wallet}
294294 walletType={props.walletType}
295+ walletHdPath={props.walletHdPath}
295296 // Tx
296297 txn={state.txn}
297298 stx={state.stx}
src/views/Trezor.js+68−36
@@ -3,6 +3,7 @@ import React from 'react'
33import Maybe from 'folktale/maybe'
44import { Button } from '../components/Base'
55import { Row, Col, H1, P } from '../components/Base'
6+import { Form, InnerLabel, Input } from '../components/Base'
67import TrezorConnect from 'trezor-connect'
78import * as secp256k1 from 'secp256k1'
89
@@ -11,57 +12,89 @@ import { ROUTE_NAMES } from '../lib/router'
1112
1213class Trezor extends React.Component {
1314
14− componentDidMount() {
15− this.pollDevice()
15+ constructor(props) {
16+ super(props)
17+
18+ this.state = {
19+ hdpath: TREZOR_BASE_PATH
20+ }
21+
22+ this.handleHdPathInput = this.handleHdPathInput.bind(this)
23+ this.pollDevice = this.pollDevice.bind(this)
24+ }
25+
26+ handleHdPathInput(hdpath) {
27+ this.setState({ hdpath })
1628 }
1729
1830 async pollDevice() {
19− const { wallet, setWallet } = this.props
20−
21− if (Maybe.Nothing.hasInstance(wallet)) {
22− TrezorConnect.getPublicKey({ path: TREZOR_BASE_PATH })
23− .then(info => {
24− if (info.success === true) {
25− const payload = info.payload
26− const publicKey = Buffer.from(payload.publicKey, 'hex')
27− const chainCode = Buffer.from(payload.chainCode, 'hex')
28− const pub = secp256k1.publicKeyConvert(publicKey, true)
29− const hd = bip32.fromPublicKey(pub, chainCode)
30− setWallet(Maybe.Just(hd))
31− } else {
32− setWallet(Maybe.Nothing())
33− }
34− })
35− }
31+ const { setWallet, setWalletHdPath } = this.props
32+ const { hdpath } = this.state
33+
34+ TrezorConnect.getPublicKey({ path: hdpath })
35+ .then(info => {
36+ if (info.success === true) {
37+ const payload = info.payload
38+ const publicKey = Buffer.from(payload.publicKey, 'hex')
39+ const chainCode = Buffer.from(payload.chainCode, 'hex')
40+ const pub = secp256k1.publicKeyConvert(publicKey, true)
41+ const hd = bip32.fromPublicKey(pub, chainCode)
42+ setWallet(Maybe.Just(hd))
43+ setWalletHdPath(hdpath)
44+ } else {
45+ setWallet(Maybe.Nothing())
46+ }
47+ })
3648 }
3749
3850 render() {
3951 const { pushRoute, popRoute, wallet } = this.props
52+ const { hdpath } = this.state
4053
4154 return (
42−
4355 <Row>
44− <Col>
56+ <Col className={'measure-md'}>
4557 <H1>{ 'Authenticate With Your Trezor' }</H1>
4658
4759 <P>
48− { 'Connect and authenticate to your Trezor. If the Trezor ' +
49− 'popup window does not display, reconnect your Trezor and ' +
50− 'navigate back to this page.'
60+ { `Connect and authenticate to your Trezor. If you'd like
61+ to use a custom derivation path, you may enter it below.`
5162 }
5263 </P>
5364
54− <Button
55− disabled={ Maybe.Nothing.hasInstance(wallet) }
56− onClick={
57− () => {
58− popRoute()
59− pushRoute(ROUTE_NAMES.SHIPS)
60− }
61− }
62− >
63− { 'Continue →' }
64− </Button>
65+ <Form>
66+ <Input
67+ className='pt-8'
68+ prop-size='md'
69+ prop-format='innerLabel'
70+ name='hdpath'
71+ value={ hdpath }
72+ autocomplete='off'
73+ onChange={ this.handleHdPathInput }>
74+ <InnerLabel>{'HD Path'}</InnerLabel>
75+ </Input>
76+
77+ <Button
78+ className={'mt-8'}
79+ prop-size={'wide lg'}
80+ onClick={ this.pollDevice }>
81+ { 'Authenticate →' }
82+ </Button>
83+
84+ <Button
85+ className={'mt-8'}
86+ prop-size={'wide lg'}
87+ disabled={ Maybe.Nothing.hasInstance(wallet) }
88+ onClick={
89+ () => {
90+ popRoute()
91+ pushRoute(ROUTE_NAMES.SHIPS)
92+ }
93+ }>
94+ { 'Continue →' }
95+ </Button>
96+
97+ </Form>
6598
6699 </Col>
67100 </Row>
@@ -70,4 +103,3 @@ class Trezor extends React.Component {
70103}
71104
72105export default Trezor
73−