on master help sign in
Merge pull request #129 from urbit/ledger-hdpaths
a0fc18fFang
src/bridge/components/InnerLabelDropdown.js+2−2
@@ -62,14 +62,14 @@ class InnerLabelDropdown extends React.Component {
62 const fullWidthClass = props.fullWidth ? 'full-width' : ''
62 const fullWidthClass = props.fullWidth ? 'full-width' : ''
63
63
64 return (
64 return (
65− <div className={`rel table ${fullWidthClass}`}>
65+ <div className={`rel table ${fullWidthClass} ${props.className}`}>
66 <div
66 <div
67 className={`fix full o-0 ${menuVisibility}`}
67 className={`fix full o-0 ${menuVisibility}`}
68 onClick={this.close}
68 onClick={this.close}
69 />
69 />
70
70
71 <button
71 <button
72− className={`h-10 mp-0 bg-white outline-blue sans ${props.className} ${fullWidthClass}`}
72+ className={`h-10 mp-0 bg-white outline-blue sans ${fullWidthClass}`}
73 onClick={this.toggle}
73 onClick={this.toggle}
74 disabled={props.disabled}>
74 disabled={props.disabled}>
75 <div className={'flex'}>
75 <div className={'flex'}>
src/bridge/lib/ledger.js+4−2
@@ -1,7 +1,8 @@
1import Transport from '@ledgerhq/hw-transport-u2f'
1import Transport from '@ledgerhq/hw-transport-u2f'
2import Eth from '@ledgerhq/hw-app-eth'
2import Eth from '@ledgerhq/hw-app-eth'
3
3
4−const LEDGER_BASE_PATH = "44'/60'/0'/0/0"
4+const LEDGER_LEGACY_PATH = "m/44'/60'/0'/x"
5+const LEDGER_LIVE_PATH = "m/44'/60'/x'/0/0"
5
6
6const chopHdPrefix = str =>
7const chopHdPrefix = str =>
7 str.slice(0, 2) === "m/"
8 str.slice(0, 2) === "m/"
@@ -24,6 +25,7 @@ const ledgerSignTransaction = async (txn, hdpath) => {
24}
25}
25
26
26export {
27export {
27− LEDGER_BASE_PATH,
28+ LEDGER_LIVE_PATH,
29+ LEDGER_LEGACY_PATH,
28 ledgerSignTransaction
30 ledgerSignTransaction
29}
31}
src/bridge/lib/trezor.js+2−2
@@ -1,6 +1,6 @@
1import TrezorConnect from 'trezor-connect'
1import TrezorConnect from 'trezor-connect'
2
2
3−const TREZOR_BASE_PATH = "m/44'/60'/0'/0/0"
3+const TREZOR_PATH = "m/44'/60'/0'/0/x"
4
4
5const formatChainId = val =>
5const formatChainId = val =>
6 typeof val === 'number'
6 typeof val === 'number'
@@ -33,7 +33,7 @@ const trezorSignTransaction = async (txn, hdpath) => {
33}
33}
34
34
35export {
35export {
36− TREZOR_BASE_PATH,
36+ TREZOR_PATH,
37 trezorSignTransaction
37 trezorSignTransaction
38}
38}
39
39
src/bridge/views/Ledger.js+86−14
@@ -3,12 +3,12 @@ import React from 'react'
3import Maybe from 'folktale/maybe'
3import Maybe from 'folktale/maybe'
4import { Button } from '../components/Base'
4import { Button } from '../components/Base'
5import { Row, Col, H1, P, H2 } from '../components/Base'
5import { Row, Col, H1, P, H2 } from '../components/Base'
6−import { Input, InnerLabel } from '../components/Base'
6+import { Input, InnerLabel, InnerLabelDropdown } from '../components/Base'
7import Transport from '@ledgerhq/hw-transport-u2f'
7import Transport from '@ledgerhq/hw-transport-u2f'
8import Eth from '@ledgerhq/hw-app-eth'
8import Eth from '@ledgerhq/hw-app-eth'
9import * as secp256k1 from 'secp256k1'
9import * as secp256k1 from 'secp256k1'
10
10
11−import { LEDGER_BASE_PATH } from '../lib/ledger'
11+import { LEDGER_LIVE_PATH, LEDGER_LEGACY_PATH } from '../lib/ledger'
12import { ROUTE_NAMES } from '../lib/router'
12import { ROUTE_NAMES } from '../lib/router'
13
13
14const chopHdPrefix = str =>
14const chopHdPrefix = str =>
@@ -26,11 +26,32 @@ class Ledger extends React.Component {
26 super(props)
26 super(props)
27
27
28 this.state = {
28 this.state = {
29− hdpath: LEDGER_BASE_PATH
29+ basePath: LEDGER_LIVE_PATH,
30+ hdpath: LEDGER_LIVE_PATH.replace(/x/g, 0),
31+ account: 0
30 }
32 }
31
33
32 this.pollDevice = this.pollDevice.bind(this)
34 this.pollDevice = this.pollDevice.bind(this)
33 this.handleHdPathInput = this.handleHdPathInput.bind(this)
35 this.handleHdPathInput = this.handleHdPathInput.bind(this)
36+ this.handlePathSelection = this.handlePathSelection.bind(this);
37+ this.handleAccountSelection = this.handleAccountSelection.bind(this);
38+ this.updateHdPath = this.updateHdPath.bind(this);
39+ }
40+
41+ handlePathSelection(basePath) {
42+ this.updateHdPath(basePath, this.state.account);
43+ }
44+
45+ handleAccountSelection(account) {
46+ this.updateHdPath(this.state.basePath, account);
47+ }
48+
49+ updateHdPath(basePath, account) {
50+ let hdpath = this.state.hdpath;
51+ if (basePath !== 'custom') {
52+ hdpath = basePath.replace(/x/g, account);
53+ }
54+ this.setState({ basePath, account, hdpath });
34 }
55 }
35
56
36 handleHdPathInput(hdpath) {
57 handleHdPathInput(hdpath) {
@@ -60,7 +81,66 @@ class Ledger extends React.Component {
60
81
61 render() {
82 render() {
62 const { pushRoute, popRoute, wallet } = this.props
83 const { pushRoute, popRoute, wallet } = this.props
63− const { hdpath } = this.state
84+ const { basePath, hdpath, account } = this.state;
85+ const { handlePathSelection, handleAccountSelection } = this;
86+
87+ const pathOptions = [
88+ { title: 'Ledger Live',
89+ value: LEDGER_LIVE_PATH
90+ },
91+ { title: 'Ledger Legacy',
92+ value: LEDGER_LEGACY_PATH
93+ },
94+ {
95+ title: 'Custom path',
96+ value: 'custom'
97+ }
98+ ];
99+ let basePathTitle = pathOptions.find(o => (o.value === basePath)).title;
100+
101+ let accountOptions = [];
102+ for (let i = 0; i < 20; i++) {
103+ accountOptions.push({
104+ title: 'Account #' + (i+1),
105+ value: i
106+ });
107+ }
108+ let accountTitle = accountOptions.find(o => (o.value === account)).title;
109+
110+ const basePathSelection = (
111+ <InnerLabelDropdown
112+ className='mt-8'
113+ options={pathOptions}
114+ handleUpdate={handlePathSelection}
115+ title={'Derivation path'}
116+ currentSelectionTitle={basePathTitle}
117+ fullWidth={true}
118+ >
119+ </InnerLabelDropdown>
120+ );
121+
122+ const truePathSelection = (basePath === 'custom')
123+ ? ( <Input
124+ className='pt-8 mt-4 text-mono'
125+ prop-size='md'
126+ prop-format='innerLabel'
127+ name='hdpath'
128+ value={ addHdPrefix(hdpath) }
129+ autocomplete='off'
130+ onChange={ this.handleHdPathInput }>
131+ <InnerLabel>{'HD Path'}</InnerLabel>
132+ </Input> )
133+ : ( <InnerLabelDropdown
134+ className='mt-4'
135+ prop-size='md'
136+ prop-format='innerLabel'
137+ options={accountOptions}
138+ handleUpdate={handleAccountSelection}
139+ title={'Account'}
140+ currentSelectionTitle={accountTitle}
141+ fullWidth={true}
142+ >
143+ </InnerLabelDropdown> );
64
144
65 return (
145 return (
66
146
@@ -99,16 +179,8 @@ class Ledger extends React.Component {
99 <li className={'mt-4'}>{'Run '}<code>{'python bridge-https.py'}</code></li>
179 <li className={'mt-4'}>{'Run '}<code>{'python bridge-https.py'}</code></li>
100 </ol>
180 </ol>
101
181
102− <Input
182+ { basePathSelection }
103− className='pt-8 mt-8 text-mono'
183+ { truePathSelection }
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>
112
184
113 <Button
185 <Button
114 prop-size={'wide lg'}
186 prop-size={'wide lg'}
src/bridge/views/Trezor.js+57−14
@@ -3,11 +3,11 @@ import React from 'react'
3import Maybe from 'folktale/maybe'
3import Maybe from 'folktale/maybe'
4import { Button } from '../components/Base'
4import { Button } from '../components/Base'
5import { Row, Col, H1, P } from '../components/Base'
5import { Row, Col, H1, P } from '../components/Base'
6−import { InnerLabel, Input } from '../components/Base'
6+import { InnerLabel, Input, InnerLabelDropdown } from '../components/Base'
7import TrezorConnect from 'trezor-connect'
7import TrezorConnect from 'trezor-connect'
8import * as secp256k1 from 'secp256k1'
8import * as secp256k1 from 'secp256k1'
9
9
10−import { TREZOR_BASE_PATH } from '../lib/trezor'
10+import { TREZOR_PATH } from '../lib/trezor'
11import { ROUTE_NAMES } from '../lib/router'
11import { ROUTE_NAMES } from '../lib/router'
12
12
13class Trezor extends React.Component {
13class Trezor extends React.Component {
@@ -16,13 +16,23 @@ class Trezor extends React.Component {
16 super(props)
16 super(props)
17
17
18 this.state = {
18 this.state = {
19− hdpath: TREZOR_BASE_PATH
19+ hdpath: TREZOR_PATH.replace(/x/g, 0),
20+ account: 0
20 }
21 }
21
22
23+ this.handleAccountSelection = this.handleAccountSelection.bind(this)
22 this.handleHdPathInput = this.handleHdPathInput.bind(this)
24 this.handleHdPathInput = this.handleHdPathInput.bind(this)
23 this.pollDevice = this.pollDevice.bind(this)
25 this.pollDevice = this.pollDevice.bind(this)
24 }
26 }
25
27
28+ handleAccountSelection(account) {
29+ let hdpath = this.state.hdpath;
30+ if (account !== 'custom') {
31+ hdpath = TREZOR_PATH.replace(/x/g, account);
32+ }
33+ this.setState({ account, hdpath });
34+ }
35+
26 handleHdPathInput(hdpath) {
36 handleHdPathInput(hdpath) {
27 this.setState({ hdpath })
37 this.setState({ hdpath })
28 }
38 }
@@ -53,7 +63,48 @@ class Trezor extends React.Component {
53
63
54 render() {
64 render() {
55 const { pushRoute, popRoute, wallet } = this.props
65 const { pushRoute, popRoute, wallet } = this.props
56− const { hdpath } = this.state
66+ const { hdpath, account } = this.state
67+ const { handleAccountSelection } = this;
68+
69+ let accountOptions = [{
70+ title: 'Custom path',
71+ value: 'custom'
72+ }];
73+ for (let i = 0; i < 20; i++) {
74+ accountOptions.push({
75+ title: 'Account #' + (i+1),
76+ value: i
77+ });
78+ }
79+ let accountTitle = accountOptions.find(o => (o.value === account)).title;
80+
81+ const accountSelection = (
82+ <InnerLabelDropdown
83+ className='mt-8'
84+ prop-size='md'
85+ prop-format='innerLabel'
86+ options={accountOptions}
87+ handleUpdate={handleAccountSelection}
88+ title={'Account'}
89+ currentSelectionTitle={accountTitle}
90+ fullWidth={true}
91+ >
92+ </InnerLabelDropdown>
93+ );
94+
95+ const pathSelection = (account !== 'custom') ? null :
96+ (
97+ <Input
98+ className='mt-8 pt-8 text-mono'
99+ prop-size='md'
100+ prop-format='innerLabel'
101+ name='hdpath'
102+ value={ hdpath }
103+ autocomplete='off'
104+ onChange={ this.handleHdPathInput }>
105+ <InnerLabel>{'HD Path'}</InnerLabel>
106+ </Input>
107+ );
57
108
58 return (
109 return (
59 <Row>
110 <Row>
@@ -66,16 +117,8 @@ class Trezor extends React.Component {
66 }
117 }
67 </P>
118 </P>
68
119
69− <Input
120+ { accountSelection }
70− className='pt-8 text-mono'
121+ { pathSelection }
71− prop-size='md'
72− prop-format='innerLabel'
73− name='hdpath'
74− value={ hdpath }
75− autocomplete='off'
76− onChange={ this.handleHdPathInput }>
77− <InnerLabel>{'HD Path'}</InnerLabel>
78− </Input>
79
122
80 <Button
123 <Button
81 className={'mt-8'}
124 className={'mt-8'}