on d760cbf help sign in
bridge/src/views/Login/Ledger.js
210 lines 5.9 kB history raw
1import React, { useCallback, useEffect } from 'react';
2import cn from 'classnames';
3import { Just, Nothing } from 'folktale/maybe';
4import {
5 P,
6 Text,
7 Input,
8 Grid,
9 H5,
10 CheckboxInput,
11 SelectInput,
12} from 'indigo-react';
13import { times } from 'lodash';
14import * as bip32 from 'bip32';
15import Transport from '@ledgerhq/hw-transport-u2f';
16import Eth from '@ledgerhq/hw-app-eth';
17import * as secp256k1 from 'secp256k1';
19import { useWallet } from 'store/wallet';
21import { ForwardButton } from 'components/Buttons';
23import {
24 useCheckboxInput,
25 useHdPathInput,
26 useSelectInput,
27} from 'lib/useInputs';
29import {
30 LEDGER_LIVE_PATH,
31 LEDGER_LEGACY_PATH,
32 chopHdPrefix,
33 addHdPrefix,
34} from 'lib/ledger';
35import { WALLET_TYPES } from 'lib/wallet';
36import useLoginView from 'lib/useLoginView';
37import useBreakpoints from 'lib/useBreakpoints';
39const PATH_OPTIONS = [
40 { text: 'Ledger Live', value: LEDGER_LIVE_PATH },
41 { text: 'Ledger Legacy', value: LEDGER_LEGACY_PATH },
42];
44const ACCOUNT_OPTIONS = times(20, i => ({
45 text: `Account #${i + 1}`,
46 value: i,
47}));
49export default function Ledger({ className }) {
50 useLoginView(WALLET_TYPES.LEDGER);
52 const { setWallet, setWalletHdPath } = useWallet();
54 // derivation path input
55 const [derivationPathInput, { data: basePathPattern }] = useSelectInput({
56 name: 'derivationpath',
57 label: 'Derivation Path',
58 placeholder: 'Choose path pattern...',
59 options: PATH_OPTIONS,
60 });
62 // account input
63 const [accountInput, { data: accountIndex }] = useSelectInput({
64 name: 'account',
65 label: 'Account',
66 placeholder: 'Choose account...',
67 options: ACCOUNT_OPTIONS,
68 });
70 // custom toggle
71 const [customPathInput, { data: useCustomPath }] = useCheckboxInput({
72 name: 'customPath',
73 label: 'Custom HD Path',
74 autoComplete: 'off',
75 initialValue: false,
76 });
78 // hd path input
79 const [
80 hdPathInput,
81 { data: hdPath },
82 { setValue: setHdPath },
83 ] = useHdPathInput({
84 name: 'hdpath',
85 label: 'HD Path',
86 initialValue: basePathPattern.replace(/x/g, 0),
87 });
89 const pollDevice = useCallback(async () => {
90 const transport = await Transport.create();
91 const eth = new Eth(transport);
92 const path = chopHdPrefix(hdPath);
94 try {
95 const info = await eth.getAddress(path, false, true);
96 const publicKey = Buffer.from(info.publicKey, 'hex');
97 const chainCode = Buffer.from(info.chainCode, 'hex');
98 const pub = secp256k1.publicKeyConvert(publicKey, true);
99 const hd = bip32.fromPublicKey(pub, chainCode);
100 setWallet(Just(hd));
101 setWalletHdPath(addHdPrefix(hdPath));
102 } catch (error) {
103 console.error(error);
104 setWallet(Nothing());
105 }
106 }, [hdPath, setWallet, setWalletHdPath]);
108 // when the base path pattern or the account index changes
109 // update the hd path in our input
110 useEffect(() => {
111 if (useCustomPath) {
112 // updated by useForm
113 } else {
114 setHdPath(basePathPattern.replace(/x/g, accountIndex));
115 }
116 }, [useCustomPath, setHdPath, basePathPattern, accountIndex]);
118 const full = useBreakpoints([true, true, false]);
119 const half = useBreakpoints([false, false, true]);
120 const isHTTPS = document.location.protocol === 'https:';
122 // when not on https, tell user how to get there
123 const renderHTTP = () => (
124 <>
125 <Grid.Item full as={H5}>
126 Running on HTTP?
127 </Grid.Item>
129 <Grid.Item full as={P}>
130 To authenticate and sign transactions with a Ledger, Bridge must be
131 serving over HTTPS on localhost. You can do this via the following:
132 </Grid.Item>
134 <Grid.Item full as="ol">
135 <li>
136 Install{' '}
137 <a
138 target="_blank"
139 href="https://github.com/FiloSottile/mkcert"
140 rel="noopener noreferrer">
141 mkcert
142 </a>
143 </li>
144 <li className="mt3">
145 Install a local certificate authority via <code>mkcert -install</code>
146 </li>
147 <li className="mt3">
148 In your <code>bridge</code> directory, generate a certificate valid
149 for localhost via <code>mkcert localhost</code>. This will produce two
150 files: <code>localhost.pem</code>, the local certificate, and
151 <code>localhost-key.pem</code> , its corresponding private key.
152 </li>
153 <li className="mt3">
154 Run <code>python bridge-https.py</code>
155 </li>
156 <li className="mt3">Return to this page.</li>
157 </Grid.Item>
158 </>
159 );
161 const renderHTTPS = () => (
162 <>
163 <Grid.Item full as={H5}>
164 Authenticate With Your Ledger
165 </Grid.Item>
166 <Grid.Item full as={Text} className="f6 mb3">
167 Connect and authenticate to your Ledger, and then open the "Ethereum"
168 application. If you're running on older firmware, make sure the "browser
169 support" option is turned on. To sign transactions, you'll also need to
170 enable the "contract data" option.
171 </Grid.Item>
173 {useCustomPath && <Grid.Item full as={Input} {...hdPathInput} />}
175 {!useCustomPath && (
176 <>
177 <Grid.Item
178 full={full}
179 half={half && 1}
180 as={SelectInput}
181 className={cn({ pr1: half })}
182 {...derivationPathInput}
183 />
184 <Grid.Item
185 full={full}
186 half={half && 2}
187 as={SelectInput}
188 className={cn({ pl1: half })}
189 {...accountInput}
190 />
191 </>
192 )}
194 <Grid.Item full as={CheckboxInput} className="mv3" {...customPathInput} />
196 <Grid.Item
197 full
198 as={ForwardButton}
199 solid
200 className="mt2"
201 onClick={pollDevice}>
202 Authenticate
203 </Grid.Item>
204 </>
205 );
207 return (
208 <Grid className={className}>{isHTTPS ? renderHTTPS() : renderHTTP()}</Grid>
209 );