@@ -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';
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';
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';
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';
19const chopHdPrefix = str => (str.slice(0, 2) === 'm/' ? str.slice(2) : str);
22const chopHdPrefix = str => (str.slice(0, 2) === 'm/' ? str.slice(2) : str);
21const addHdPrefix = str => (str.slice(0, 2) === 'm/' ? str : 'm/' + str);
24const addHdPrefix = str => (str.slice(0, 2) === 'm/' ? str : 'm/' + str);
23−class Ledger extends React.Component {
26+export default function Ledger({ loginCompleted }) {
24− constructor(props) {
27+ const { wallet, setWallet, setWalletHdPath } = useWallet();
28− basePath: LEDGER_LIVE_PATH,
29− hdpath: 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);
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));
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));
53− this.setState({ basePath, account, hdpath });
37+ };
56− handleHdPathInput(hdpath) {
39+ const handlePathSelection = basePath => {
57− this.setState({ hdpath });
40+ setBasePath(basePath);
41+ updateHdPath(basePath, account);
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);
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);
68 eth.getAddress(path, false, true).then(
54 eth.getAddress(path, false, true).then(
@@ -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));
78 setWallet(Maybe.Nothing());
64 setWallet(Maybe.Nothing());
69+ const pathOptions = [
70+ { title: 'Ledger Live', value: LEDGER_LIVE_PATH },
71+ { title: 'Ledger Legacy', value: LEDGER_LEGACY_PATH },
73+ title: 'Custom path',
77+ const basePathTitle = pathOptions.find(o => o.value === basePath).title;
79+ let accountOptions = [];
80+ for (let i = 0; i < 20; i++) {
81+ accountOptions.push({
82+ title: 'Account #' + (i + 1),
82−
86+ const accountTitle = accountOptions.find(o => o.value === account).title;
84− const { wallet } = this.props;
88+ const basePathSelection = (
85− const { basePath, hdpath, account } = this.state;
89+ <InnerLabelDropdown
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+ />
92− title: 'Custom path',
98+
99+ const truePathSelection =
93− value: 'custom',
100+ basePath === 'custom' ? (
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>
105− let accountTitle = accountOptions.find(o => o.value === account).title;
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}
113− currentSelectionTitle={basePathTitle}
119+ currentSelectionTitle={accountTitle}
114 fullWidth={true}
120 fullWidth={true}
118− const truePathSelection =
119− basePath === 'custom' ? (
121− className="pt-8 mt-4 text-mono"
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}
143− return (
124+ return (
145− <Col className={'measure-md'}>
146− <H1>{'Authenticate With Your Ledger'}</H1>
126+ <H1>{'Authenticate With Your Ledger'}</H1>
148− <H2>{'Running on HTTPS?'}</H2>
128+ <H2>{'Running on HTTPS?'}</H2>
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.`}
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.`}
163− <H2>{'Running on HTTP?'}</H2>
143+ <H2>{'Running on HTTP?'}</H2>
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>
169− <ol className={'measure-md'}>
149+ <ol className={'measure-md'}>
170− <li className={'mt-4'}>
150+ <li className={'mt-4'}>
171− {'Install'}{' '}
151+ {'Install'}{' '}
174− href={'https://github.com/FiloSottile/mkcert'}>
152+ <a target={'_blank'} href={'https://github.com/FiloSottile/mkcert'}>
175− {'mkcert'}
153+ {'mkcert'}
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>
182− <li className={'mt-4'}>
160+ <li className={'mt-4'}>
183− {'In your '}
161+ {'In your '}
184− <code>{'bridge'}</code>
162+ <code>{'bridge'}</code>
186− ' directory, generate a certificate valid for localhost via '
163+ {' directory, generate a certificate valid for localhost via '}{' '}
188− <code>{'mkcert localhost'}</code>
164+ <code>{'mkcert localhost'}</code>
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.'}
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>
201− {basePathSelection}
177+ {basePathSelection}
202− {truePathSelection}
178+ {truePathSelection}
205− prop-size={'wide lg'}
207− onClick={this.pollDevice}>
180+ <ForwardButton className={'mt3'} onClick={pollDevice}>
208− {'Authenticate →'}
181+ {'Authenticate'}
209− </Button>
182+ </ForwardButton>
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>
224−export default compose(withWallet)(Ledger);