@@ -1,6 +1,6 @@
1−import React, { useState } from 'react';
1+import React, { useCallback, useEffect, useState } from 'react';
2import Maybe from 'folktale/maybe';
2import 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';
4import { times } from 'lodash';
4import { times } from 'lodash';
5import * as bip32 from 'bip32';
5import * as bip32 from 'bip32';
6import Transport from '@ledgerhq/hw-transport-u2f';
6import Transport from '@ledgerhq/hw-transport-u2f';
@@ -9,17 +9,22 @@ import * as secp256k1 from 'secp256k1';
10import { ForwardButton } from 'components/Buttons';
10import { ForwardButton } from 'components/Buttons';
11import { InnerLabelDropdown } from 'components/old/Base';
11import { InnerLabelDropdown } from 'components/old/Base';
12+import { useCheckboxInput, useHdPathInput } from 'components/Inputs';
13import { useWallet } from 'store/wallet';
14import { useWallet } from 'store/wallet';
15−import { LEDGER_LIVE_PATH, LEDGER_LEGACY_PATH } from 'lib/ledger';
18+ LEDGER_LEGACY_PATH,
16import { WALLET_TYPES } from 'lib/wallet';
22import { WALLET_TYPES } from 'lib/wallet';
17import useLoginView from 'lib/useLoginView';
23import useLoginView from 'lib/useLoginView';
19const pathOptions = [
25const pathOptions = [
20 { title: 'Ledger Live', value: LEDGER_LIVE_PATH },
26 { title: 'Ledger Live', value: LEDGER_LIVE_PATH },
21 { title: 'Ledger Legacy', value: LEDGER_LEGACY_PATH },
27 { title: 'Ledger Legacy', value: LEDGER_LEGACY_PATH },
22− { title: 'Custom path', value: 'custom' },
25const accountOptions = times(20, i => ({
30const accountOptions = times(20, i => ({
@@ -27,58 +32,70 @@ const accountOptions = times(20, i => ({
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);
33export default function Ledger({ className }) {
35export default function Ledger({ className }) {
34 useLoginView(WALLET_TYPES.LEDGER);
36 useLoginView(WALLET_TYPES.LEDGER);
36 const { setWallet, setWalletHdPath } = useWallet();
38 const { setWallet, setWalletHdPath } = useWallet();
40+ // ledger has two derivation path _patterns_
38− const [basePath, setBasePath] = useState(LEDGER_LIVE_PATH);
41+ const [basePathPattern, setBasePathPattern] = useState(LEDGER_LIVE_PATH);
39− const [account, setAccount] = useState(0);
42+ // and then we want to be able to select an index within that path
40− const [hdPath, setHdPath] = useState(LEDGER_LIVE_PATH.replace(/x/g, 0));
43+ const [accountIndex, setAccountIndex] = useState(0);
42− const updateHdPath = (basePath, account) => {
43− if (basePath !== 'custom') {
45+ // custom toggle
44− setHdPath(basePath.replace(/x/g, account));
46+ const [customPathInput, { data: useCustomPath }] = useCheckboxInput({
45− }
47+ name: 'customPath',
48+ label: 'Custom HD Path',
48− const handlePathSelection = basePath => {
53+ // hd path input
49− setBasePath(basePath);
54+ const [
50− updateHdPath(basePath, account);
55+ hdPathInput,
51− };
56+ { data: hdPath },
52−
57+ { setValue: setHdPath },
53− const handleAccountSelection = account => {
58+ ] = useHdPathInput({
54− setAccount(account);
59+ name: 'hdpath',
55− updateHdPath(basePath, account);
60+ label: 'HD Path',
61+ initialValue: basePathPattern.replace(/x/g, 0),
58− const pollDevice = async () => {
64+ const pollDevice = useCallback(async () => {
59 const transport = await Transport.create();
65 const transport = await Transport.create();
60 const eth = new Eth(transport);
66 const eth = new Eth(transport);
61 const path = chopHdPrefix(hdPath);
67 const path = chopHdPrefix(hdPath);
63− eth.getAddress(path, false, true).then(
70+ const info = await eth.getAddress(path, false, true);
65− const publicKey = Buffer.from(info.publicKey, 'hex');
71+ const publicKey = Buffer.from(info.publicKey, 'hex');
66− const chainCode = Buffer.from(info.chainCode, 'hex');
72+ const chainCode = Buffer.from(info.chainCode, 'hex');
67− const pub = secp256k1.publicKeyConvert(publicKey, true);
73+ const pub = secp256k1.publicKeyConvert(publicKey, true);
68− const hd = bip32.fromPublicKey(pub, chainCode);
74+ const hd = bip32.fromPublicKey(pub, chainCode);
69− setWallet(Maybe.Just(hd));
75+ setWallet(Maybe.Just(hd));
70− setWalletHdPath(addHdPrefix(hdPath));
76+ setWalletHdPath(addHdPrefix(hdPath));
71− },
77+ } catch (error) {
72− _ => {
78+ console.error(error);
73− setWallet(Maybe.Nothing());
79+ setWallet(Maybe.Nothing());
75− );
81+ }, [hdPath, setWallet, setWalletHdPath]);
77−
83+ // when the base path pattern or the account index changes
84+ // update the hd path in our input
87+ // updated by useForm
89+ setHdPath(basePathPattern.replace(/x/g, accountIndex));
91+ }, [useCustomPath, setHdPath, basePathPattern, accountIndex]);
78− const basePathTitle = pathOptions.find(o => o.value === basePath).title;
93+ const basePathPatternTitle = pathOptions.find(
94+ o => o.value === basePathPattern
79− const accountTitle = accountOptions.find(o => o.value === account).title;
96+ const accountTitle = accountOptions.find(o => o.value === accountIndex).title;
81− const isHTTPS = document.location.protocol === 'https:';
98+ const isHTTPS = document.location.protocol === 'http:';
83 // when not on https, tell user how to get there
100 // when not on https, tell user how to get there
84 const renderHTTP = () => (
101 const renderHTTP = () => (
@@ -114,67 +131,65 @@ export default function Ledger({ className }) {
114 <li className="mt3">
131 <li className="mt3">
115 Run <code>python bridge-https.py</code>
132 Run <code>python bridge-https.py</code>
134+ <li className="mt3">Return to this page.</li>
117 </Grid.Item>
135 </Grid.Item>
121 const renderHTTPS = () => (
139 const renderHTTPS = () => (
123− <Grid.Item full as={P}>
141+ <Grid.Item full as={H5}>
142+ Authenticate With Your Ledger
144+ <Grid.Item full as={Text} className="f6">
124 Connect and authenticate to your Ledger, and then open the "Ethereum"
145 Connect and authenticate to your Ledger, and then open the "Ethereum"
125 application. If you're running on older firmware, make sure the "browser
146 application. If you're running on older firmware, make sure the "browser
126 support" option is turned on. To sign transactions, you'll also need to
147 support" option is turned on. To sign transactions, you'll also need to
127 enable the "contract data" option.
148 enable the "contract data" option.
128 </Grid.Item>
149 </Grid.Item>
130− <Grid.Item full as={P}>
152+ <Grid.Item full as={Input} className="mv3" {...hdPathInput} />
131− If you'd like to use a custom derivation path, you may enter it below.
153+ )}
155+ {!useCustomPath && (
134− <Grid.Item
157+ <Grid.Item
136− as={InnerLabelDropdown}
159+ as={InnerLabelDropdown}
137− className="mv4"
160+ className="mv4"
138− title="Derivation path"
161+ title="Derivation path"
139− options={pathOptions}
162+ options={pathOptions}
140− handleUpdate={handlePathSelection}
163+ handleUpdate={setBasePathPattern}
141− currentSelectionTitle={basePathTitle}
164+ currentSelectionTitle={basePathPatternTitle}
144− {basePath === 'custom' ? (
152− initialValue={addHdPrefix(hdPath)}
156− <Grid.Item
166+ <Grid.Item
158− as={InnerLabelDropdown}
168+ as={InnerLabelDropdown}
159− className="mt4"
169+ className="mt4"
160− title="Account"
170+ title="Account"
161− options={accountOptions}
171+ options={accountOptions}
162− handleUpdate={handleAccountSelection}
172+ handleUpdate={setAccountIndex}
163− currentSelectionTitle={accountTitle}
173+ currentSelectionTitle={accountTitle}
178+ <Grid.Item full as={CheckboxInput} {...customPathInput} />
167− <ForwardButton className="mt3" onClick={pollDevice}>
185+ onClick={pollDevice}>
168 Authenticate
186 Authenticate
169− </ForwardButton>
187+ </Grid.Item>
174 <Grid className={className}>
192 <Grid className={className}>
175− <Grid.Item full as={H4}>
176− Authenticate With Your Ledger
178 {isHTTPS && renderHTTPS()}
193 {isHTTPS && renderHTTPS()}
179 {!isHTTPS && renderHTTP()}
194 {!isHTTPS && renderHTTP()}