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