on master help sign in
Make /views/Login/Ledger functional/stateless
06a779fFang
src/views/Login/Ledger.js+149−181
@@ -1,69 +1,55 @@
1import * as bip32 from 'bip32';
1import * as bip32 from 'bip32';
2−import React from 'react';
2+import React, { useState } from 'react';
3import Maybe from 'folktale/maybe';
3import Maybe from 'folktale/maybe';
4+import Transport from '@ledgerhq/hw-transport-u2f';
5+import Eth from '@ledgerhq/hw-app-eth';
6+import * as secp256k1 from 'secp256k1';
7+
4−import { Button } from '../../components/old/Base';
8+import View from '../../components/View';
5−import { Row, Col, H1, P, H2 } from '../../components/old/Base';
9+import { ForwardButton } from '../../components/Buttons';
6import {
10import {
11+ H1,
12+ P,
13+ H2,
7 Input,
14 Input,
8 InnerLabel,
15 InnerLabel,
9 InnerLabelDropdown,
16 InnerLabelDropdown,
10} from '../../components/old/Base';
17} from '../../components/old/Base';
11−import Transport from '@ledgerhq/hw-transport-u2f';
12−import Eth from '@ledgerhq/hw-app-eth';
13−import * as secp256k1 from 'secp256k1';
14
18
15import { LEDGER_LIVE_PATH, LEDGER_LEGACY_PATH } from '../../lib/ledger';
19import { LEDGER_LIVE_PATH, LEDGER_LEGACY_PATH } from '../../lib/ledger';
16−import { compose } from '../../lib/lib';
17−import { withWallet } from '../../store/wallet';
20+import { useWallet } from '../../store/wallet';
18
21
19const chopHdPrefix = str => (str.slice(0, 2) === 'm/' ? str.slice(2) : str);
22const chopHdPrefix = str => (str.slice(0, 2) === 'm/' ? str.slice(2) : str);
20
23
21const addHdPrefix = str => (str.slice(0, 2) === 'm/' ? str : 'm/' + str);
24const addHdPrefix = str => (str.slice(0, 2) === 'm/' ? str : 'm/' + str);
22
25
23−class Ledger extends React.Component {
26+export default function Ledger({ loginCompleted }) {
24− constructor(props) {
27+ const { wallet, setWallet, setWalletHdPath } = useWallet();
25− super(props);
26−
27− this.state = {
28− basePath: LEDGER_LIVE_PATH,
29− hdpath: LEDGER_LIVE_PATH.replace(/x/g, 0),
30− account: 0,
31− };
32−
33− this.pollDevice = this.pollDevice.bind(this);
34− this.handleHdPathInput = this.handleHdPathInput.bind(this);
35− this.handlePathSelection = this.handlePathSelection.bind(this);
36− this.handleAccountSelection = this.handleAccountSelection.bind(this);
37− this.updateHdPath = this.updateHdPath.bind(this);
38− }
39−
40− handlePathSelection(basePath) {
41− this.updateHdPath(basePath, this.state.account);
42− }
43
28
44− handleAccountSelection(account) {
29+ const [basePath, setBasePath] = useState(LEDGER_LIVE_PATH);
45− this.updateHdPath(this.state.basePath, account);
30+ const [account, setAccount] = useState(0);
46− }
31+ const [hdPath, setHdPath] = useState(LEDGER_LIVE_PATH.replace(/x/g, 0));
47
32
48− updateHdPath(basePath, account) {
33+ const updateHdPath = (basePath, account) => {
49− let hdpath = this.state.hdpath;
50 if (basePath !== 'custom') {
34 if (basePath !== 'custom') {
51− hdpath = basePath.replace(/x/g, account);
35+ setHdPath(basePath.replace(/x/g, account));
52 }
36 }
53− this.setState({ basePath, account, hdpath });
37+ };
54− }
55
38
56− handleHdPathInput(hdpath) {
39+ const handlePathSelection = basePath => {
57− this.setState({ hdpath });
40+ setBasePath(basePath);
41+ updateHdPath(basePath, account);
58− }
42+ };
59
43
60− async pollDevice() {
44+ const handleAccountSelection = account => {
61− const { setWallet, setWalletHdPath } = this.props;
45+ setAccount(account);
62− const { hdpath } = this.state;
46+ updateHdPath(basePath, account);
47+ };
63
48
49+ const pollDevice = async () => {
64 const transport = await Transport.create();
50 const transport = await Transport.create();
65 const eth = new Eth(transport);
51 const eth = new Eth(transport);
66− const path = chopHdPrefix(hdpath);
52+ const path = chopHdPrefix(hdPath);
67
53
68 eth.getAddress(path, false, true).then(
54 eth.getAddress(path, false, true).then(
69 info => {
55 info => {
@@ -72,153 +58,135 @@ class Ledger extends React.Component {
72 const pub = secp256k1.publicKeyConvert(publicKey, true);
58 const pub = secp256k1.publicKeyConvert(publicKey, true);
73 const hd = bip32.fromPublicKey(pub, chainCode);
59 const hd = bip32.fromPublicKey(pub, chainCode);
74 setWallet(Maybe.Just(hd));
60 setWallet(Maybe.Just(hd));
75− setWalletHdPath(addHdPrefix(hdpath));
61+ setWalletHdPath(addHdPrefix(hdPath));
76 },
62 },
77 _ => {
63 _ => {
78 setWallet(Maybe.Nothing());
64 setWallet(Maybe.Nothing());
79 }
65 }
80 );
66 );
67+ };
68+
69+ const pathOptions = [
70+ { title: 'Ledger Live', value: LEDGER_LIVE_PATH },
71+ { title: 'Ledger Legacy', value: LEDGER_LEGACY_PATH },
72+ {
73+ title: 'Custom path',
74+ value: 'custom',
75+ },
76+ ];
77+ const basePathTitle = pathOptions.find(o => o.value === basePath).title;
78+
79+ let accountOptions = [];
80+ for (let i = 0; i < 20; i++) {
81+ accountOptions.push({
82+ title: 'Account #' + (i + 1),
83+ value: i,
84+ });
81 }
85 }
82−
86+ const accountTitle = accountOptions.find(o => o.value === account).title;
83− render() {
87+
84− const { wallet } = this.props;
88+ const basePathSelection = (
85− const { basePath, hdpath, account } = this.state;
89+ <InnerLabelDropdown
90+ className="mt-8"
91+ options={pathOptions}
86− const { handlePathSelection, handleAccountSelection } = this;
92+ handleUpdate={handlePathSelection}
87−
93+ title={'Derivation path'}
88− const pathOptions = [
94+ currentSelectionTitle={basePathTitle}
89− { title: 'Ledger Live', value: LEDGER_LIVE_PATH },
95+ fullWidth={true}
90− { title: 'Ledger Legacy', value: LEDGER_LEGACY_PATH },
96+ />
91− {
97+ );
92− title: 'Custom path',
98+
99+ const truePathSelection =
93− value: 'custom',
100+ basePath === 'custom' ? (
94− },
101+ <Input
95− ];
102+ className="pt-8 mt-4 text-mono"
96− let basePathTitle = pathOptions.find(o => o.value === basePath).title;
103+ prop-size="md"
97−
104+ prop-format="innerLabel"
98− let accountOptions = [];
105+ name="hdPath"
99− for (let i = 0; i < 20; i++) {
106+ value={addHdPrefix(hdPath)}
100− accountOptions.push({
107+ autocomplete="off"
101− title: 'Account #' + (i + 1),
108+ onChange={setHdPath}>
102− value: i,
109+ <InnerLabel>{'HD Path'}</InnerLabel>
103− });
110+ </Input>
104− }
111+ ) : (
105− let accountTitle = accountOptions.find(o => o.value === account).title;
106−
107− const basePathSelection = (
108 <InnerLabelDropdown
112 <InnerLabelDropdown
109− className="mt-8"
113+ className="mt-4"
110− options={pathOptions}
114+ prop-size="md"
111− handleUpdate={handlePathSelection}
115+ prop-format="innerLabel"
112− title={'Derivation path'}
116+ options={accountOptions}
117+ handleUpdate={handleAccountSelection}
118+ title={'Account'}
113− currentSelectionTitle={basePathTitle}
119+ currentSelectionTitle={accountTitle}
114 fullWidth={true}
120 fullWidth={true}
115 />
121 />
116 );
122 );
117
123
118− const truePathSelection =
119− basePath === 'custom' ? (
120− <Input
121− className="pt-8 mt-4 text-mono"
122− prop-size="md"
123− prop-format="innerLabel"
124− name="hdpath"
125− value={addHdPrefix(hdpath)}
126− autocomplete="off"
127− onChange={this.handleHdPathInput}>
128− <InnerLabel>{'HD Path'}</InnerLabel>
129− </Input>
130− ) : (
131− <InnerLabelDropdown
132− className="mt-4"
133− prop-size="md"
134− prop-format="innerLabel"
135− options={accountOptions}
136− handleUpdate={handleAccountSelection}
137− title={'Account'}
138− currentSelectionTitle={accountTitle}
139− fullWidth={true}
140− />
141− );
142−
143− return (
124+ return (
144− <Row>
125+ <View>
145− <Col className={'measure-md'}>
146− <H1>{'Authenticate With Your Ledger'}</H1>
126+ <H1>{'Authenticate With Your Ledger'}</H1>
147−
127+
148− <H2>{'Running on HTTPS?'}</H2>
128+ <H2>{'Running on HTTPS?'}</H2>
149−
129+
150− <P>
130+ <P>
151− {`Connect and authenticate to your Ledger, and then open the
131+ {`Connect and authenticate to your Ledger, and then open the
152− "Ethereum" application. If you're running on older firmware,
132+ "Ethereum" application. If you're running on older firmware,
153− make sure the "browser support" option is turned on. To sign
133+ make sure the "browser support" option is turned on. To sign
154− transactions, you'll also need to enable the "contract data"
134+ transactions, you'll also need to enable the "contract data"
155− option.`}
135+ option.`}
156− </P>
136+ </P>
157−
137+
158− <P>
138+ <P>
159− {`If you'd like to use a custom derivation path, you may enter
139+ {`If you'd like to use a custom derivation path, you may enter
160− it below.`}
140+ it below.`}
161− </P>
141+ </P>
162−
142+
163− <H2>{'Running on HTTP?'}</H2>
143+ <H2>{'Running on HTTP?'}</H2>
164−
144+
165− <P>{`To authenticate and sign transactions with a Ledger, Bridge
145+ <P>{`To authenticate and sign transactions with a Ledger, Bridge
166− must be serving over HTTPS on localhost. You can do this via the
146+ must be serving over HTTPS on localhost. You can do this via the
167− following:`}</P>
147+ following:`}</P>
168−
148+
169− <ol className={'measure-md'}>
149+ <ol className={'measure-md'}>
170− <li className={'mt-4'}>
150+ <li className={'mt-4'}>
171− {'Install'}{' '}
151+ {'Install'}{' '}
172− <a
173− target={'_blank'}
174− href={'https://github.com/FiloSottile/mkcert'}>
152+ <a target={'_blank'} href={'https://github.com/FiloSottile/mkcert'}>
175− {'mkcert'}
153+ {'mkcert'}
176− </a>
154+ </a>
177− </li>
155+ </li>
178− <li className={'mt-4'}>
156+ <li className={'mt-4'}>
179− {'Install a local certificate authority via '}
157+ {'Install a local certificate authority via '}
180− <code>{'mkcert -install'}</code>
158+ <code>{'mkcert -install'}</code>
181− </li>
159+ </li>
182− <li className={'mt-4'}>
160+ <li className={'mt-4'}>
183− {'In your '}
161+ {'In your '}
184− <code>{'bridge'}</code>
162+ <code>{'bridge'}</code>
185− {
186− ' directory, generate a certificate valid for localhost via '
163+ {' directory, generate a certificate valid for localhost via '}{' '}
187− }{' '}
188− <code>{'mkcert localhost'}</code>
164+ <code>{'mkcert localhost'}</code>
189− {'.'}
165+ {'.'}
190− {'This will produce two files: '}
166+ {'This will produce two files: '}
191− <code>{'localhost.pem'}</code> {', the local certificate, and '}
167+ <code>{'localhost.pem'}</code> {', the local certificate, and '}
192− <code>{'localhost-key.pem'}</code>
168+ <code>{'localhost-key.pem'}</code>
193− {', its corresponding private key.'}
169+ {', its corresponding private key.'}
194− </li>
170+ </li>
195− <li className={'mt-4'}>
171+ <li className={'mt-4'}>
196− {'Run '}
172+ {'Run '}
197− <code>{'python bridge-https.py'}</code>
173+ <code>{'python bridge-https.py'}</code>
198− </li>
174+ </li>
199− </ol>
175+ </ol>
200−
176+
201− {basePathSelection}
177+ {basePathSelection}
202− {truePathSelection}
178+ {truePathSelection}
203−
179+
204− <Button
205− prop-size={'wide lg'}
206− className={'mt-8'}
207− onClick={this.pollDevice}>
180+ <ForwardButton className={'mt3'} onClick={pollDevice}>
208− {'Authenticate →'}
181+ {'Authenticate'}
209− </Button>
182+ </ForwardButton>
210−
183+
211− <Button
184+ <ForwardButton
212− className={'mt-8'}
185+ className={'mt3'}
213− prop-size={'wide lg'}
214− disabled={Maybe.Nothing.hasInstance(wallet)}
186+ disabled={Maybe.Nothing.hasInstance(wallet)}
215− onClick={this.props.loginCompleted}>
187+ onClick={loginCompleted}>
216− {'Continue →'}
188+ {'Continue'}
217− </Button>
189+ </ForwardButton>
218− </Col>
190+ </View>
219− </Row>
220− );
191+ );
221− }
222}
192}
223−
224−export default compose(withWallet)(Ledger);