@@ -1,6 +1,6 @@
1−import React, { useState } from 'react';
1+import React, { useCallback, useEffect, useState } from 'react';
22import Maybe from 'folktale/maybe';
3−import { P, Input, Grid, H4, H5 } from 'indigo-react';
3+import { P, Text, Input, Grid, H5, CheckboxInput } from 'indigo-react';
44import { times } from 'lodash';
55import * as bip32 from 'bip32';
66import Transport from '@ledgerhq/hw-transport-u2f';
@@ -9,17 +9,22 @@ import * as secp256k1 from 'secp256k1';
99
1010import { ForwardButton } from 'components/Buttons';
1111import { InnerLabelDropdown } from 'components/old/Base';
12+import { useCheckboxInput, useHdPathInput } from 'components/Inputs';
1213
1314import { useWallet } from 'store/wallet';
1415
15−import { LEDGER_LIVE_PATH, LEDGER_LEGACY_PATH } from 'lib/ledger';
16+import {
17+ LEDGER_LIVE_PATH,
18+ LEDGER_LEGACY_PATH,
19+ chopHdPrefix,
20+ addHdPrefix,
21+} from 'lib/ledger';
1622import { WALLET_TYPES } from 'lib/wallet';
1723import useLoginView from 'lib/useLoginView';
1824
1925const pathOptions = [
2026 { title: 'Ledger Live', value: LEDGER_LIVE_PATH },
2127 { title: 'Ledger Legacy', value: LEDGER_LEGACY_PATH },
22− { title: 'Custom path', value: 'custom' },
2328];
2429
2530const accountOptions = times(20, i => ({
@@ -27,58 +32,70 @@ const accountOptions = times(20, i => ({
2732 value: i,
2833}));
2934
30−const chopHdPrefix = str => (str.slice(0, 2) === 'm/' ? str.slice(2) : str);
31−const addHdPrefix = str => (str.slice(0, 2) === 'm/' ? str : 'm/' + str);
32−
3335export default function Ledger({ className }) {
3436 useLoginView(WALLET_TYPES.LEDGER);
3537
3638 const { setWallet, setWalletHdPath } = useWallet();
3739
38− const [basePath, setBasePath] = useState(LEDGER_LIVE_PATH);
39− const [account, setAccount] = useState(0);
40− const [hdPath, setHdPath] = useState(LEDGER_LIVE_PATH.replace(/x/g, 0));
41−
42− const updateHdPath = (basePath, account) => {
43− if (basePath !== 'custom') {
44− setHdPath(basePath.replace(/x/g, account));
45− }
46− };
47−
48− const handlePathSelection = basePath => {
49− setBasePath(basePath);
50− updateHdPath(basePath, account);
51− };
52−
53− const handleAccountSelection = account => {
54− setAccount(account);
55− updateHdPath(basePath, account);
56− };
57−
58− const pollDevice = async () => {
40+ // ledger has two derivation path _patterns_
41+ const [basePathPattern, setBasePathPattern] = useState(LEDGER_LIVE_PATH);
42+ // and then we want to be able to select an index within that path
43+ const [accountIndex, setAccountIndex] = useState(0);
44+
45+ // custom toggle
46+ const [customPathInput, { data: useCustomPath }] = useCheckboxInput({
47+ name: 'customPath',
48+ label: 'Custom HD Path',
49+ autoComplete: 'off',
50+ initialValue: false,
51+ });
52+
53+ // hd path input
54+ const [
55+ hdPathInput,
56+ { data: hdPath },
57+ { setValue: setHdPath },
58+ ] = useHdPathInput({
59+ name: 'hdpath',
60+ label: 'HD Path',
61+ initialValue: basePathPattern.replace(/x/g, 0),
62+ });
63+
64+ const pollDevice = useCallback(async () => {
5965 const transport = await Transport.create();
6066 const eth = new Eth(transport);
6167 const path = chopHdPrefix(hdPath);
6268
63− eth.getAddress(path, false, true).then(
64− info => {
65− const publicKey = Buffer.from(info.publicKey, 'hex');
66− const chainCode = Buffer.from(info.chainCode, 'hex');
67− const pub = secp256k1.publicKeyConvert(publicKey, true);
68− const hd = bip32.fromPublicKey(pub, chainCode);
69− setWallet(Maybe.Just(hd));
70− setWalletHdPath(addHdPrefix(hdPath));
71− },
72− _ => {
73− setWallet(Maybe.Nothing());
74− }
75− );
76− };
77−
78− const basePathTitle = pathOptions.find(o => o.value === basePath).title;
79− const accountTitle = accountOptions.find(o => o.value === account).title;
80−
81− const isHTTPS = document.location.protocol === 'https:';
69+ try {
70+ const info = await eth.getAddress(path, false, true);
71+ const publicKey = Buffer.from(info.publicKey, 'hex');
72+ const chainCode = Buffer.from(info.chainCode, 'hex');
73+ const pub = secp256k1.publicKeyConvert(publicKey, true);
74+ const hd = bip32.fromPublicKey(pub, chainCode);
75+ setWallet(Maybe.Just(hd));
76+ setWalletHdPath(addHdPrefix(hdPath));
77+ } catch (error) {
78+ console.error(error);
79+ setWallet(Maybe.Nothing());
80+ }
81+ }, [hdPath, setWallet, setWalletHdPath]);
82+
83+ // when the base path pattern or the account index changes
84+ // update the hd path in our input
85+ useEffect(() => {
86+ if (useCustomPath) {
87+ // updated by useForm
88+ } else {
89+ setHdPath(basePathPattern.replace(/x/g, accountIndex));
90+ }
91+ }, [useCustomPath, setHdPath, basePathPattern, accountIndex]);
92+
93+ const basePathPatternTitle = pathOptions.find(
94+ o => o.value === basePathPattern
95+ ).title;
96+ const accountTitle = accountOptions.find(o => o.value === accountIndex).title;
97+
98+ const isHTTPS = document.location.protocol === 'http:';
8299
83100 // when not on https, tell user how to get there
84101 const renderHTTP = () => (
@@ -114,67 +131,65 @@ export default function Ledger({ className }) {
114131 <li className="mt3">
115132 Run <code>python bridge-https.py</code>
116133 </li>
134+ <li className="mt3">Return to this page.</li>
117135 </Grid.Item>
118136 </>
119137 );
120138
121139 const renderHTTPS = () => (
122140 <>
123− <Grid.Item full as={P}>
141+ <Grid.Item full as={H5}>
142+ Authenticate With Your Ledger
143+ </Grid.Item>
144+ <Grid.Item full as={Text} className="f6">
124145 Connect and authenticate to your Ledger, and then open the "Ethereum"
125146 application. If you're running on older firmware, make sure the "browser
126147 support" option is turned on. To sign transactions, you'll also need to
127148 enable the "contract data" option.
128149 </Grid.Item>
129150
130− <Grid.Item full as={P}>
131− If you'd like to use a custom derivation path, you may enter it below.
132− </Grid.Item>
151+ {useCustomPath && (
152+ <Grid.Item full as={Input} className="mv3" {...hdPathInput} />
153+ )}
133154
134− <Grid.Item
135− full
136− as={InnerLabelDropdown}
137− className="mv4"
138− title="Derivation path"
139− options={pathOptions}
140− handleUpdate={handlePathSelection}
141− currentSelectionTitle={basePathTitle}
142− />
143−
144− {basePath === 'custom' ? (
145− <Grid.Item
146− full
147− as={Input}
148− className="mv3"
149− name="hdPath"
150− label="HD path"
151− autoComplete="off"
152− initialValue={addHdPrefix(hdPath)}
153− onValue={setHdPath}
154− />
155− ) : (
156− <Grid.Item
157− full
158− as={InnerLabelDropdown}
159− className="mt4"
160− title="Account"
161− options={accountOptions}
162− handleUpdate={handleAccountSelection}
163− currentSelectionTitle={accountTitle}
164− />
155+ {!useCustomPath && (
156+ <>
157+ <Grid.Item
158+ full
159+ as={InnerLabelDropdown}
160+ className="mv4"
161+ title="Derivation path"
162+ options={pathOptions}
163+ handleUpdate={setBasePathPattern}
164+ currentSelectionTitle={basePathPatternTitle}
165+ />
166+ <Grid.Item
167+ full
168+ as={InnerLabelDropdown}
169+ className="mt4"
170+ title="Account"
171+ options={accountOptions}
172+ handleUpdate={setAccountIndex}
173+ currentSelectionTitle={accountTitle}
174+ />
175+ </>
165176 )}
166177
167− <ForwardButton className="mt3" onClick={pollDevice}>
178+ <Grid.Item full as={CheckboxInput} {...customPathInput} />
179+
180+ <Grid.Item
181+ full
182+ as={ForwardButton}
183+ solid
184+ className="mt3"
185+ onClick={pollDevice}>
168186 Authenticate
169− </ForwardButton>
187+ </Grid.Item>
170188 </>
171189 );
172190
173191 return (
174192 <Grid className={className}>
175− <Grid.Item full as={H4}>
176− Authenticate With Your Ledger
177− </Grid.Item>
178193 {isHTTPS && renderHTTPS()}
179194 {!isHTTPS && renderHTTP()}
180195 </Grid>