on c19b2a8 help sign in
bridge/src/views/Login/Ledger.js
221 lines 6.6 kB history raw
1import React, { useCallback, useMemo } from 'react';
2import cn from 'classnames';
3import { Just } from 'folktale/maybe';
4import { P, Text, Grid, H5, CheckboxInput, SelectInput } from 'indigo-react';
5import { times } from 'lodash';
6import * as bip32 from 'bip32';
7import Transport from '@ledgerhq/hw-transport-u2f';
8import Eth from '@ledgerhq/hw-app-eth';
9import * as secp256k1 from 'secp256k1';
11import { useWallet } from 'store/wallet';
13import {
14 LEDGER_LIVE_PATH,
15 LEDGER_LEGACY_PATH,
16 chopHdPrefix,
17 addHdPrefix,
18} from 'lib/ledger';
19import { WALLET_TYPES } from 'lib/wallet';
20import useLoginView from 'lib/useLoginView';
21import useBreakpoints from 'lib/useBreakpoints';
22import {
23 composeValidator,
24 buildCheckboxValidator,
25 buildHdPathValidator,
26 buildSelectValidator,
27} from 'form/validators';
28import BridgeForm from 'form/BridgeForm';
29import FormError from 'form/FormError';
30import SubmitButton from 'form/SubmitButton';
31import Condition from 'form/Condition';
32import { FORM_ERROR } from 'final-form';
33import { HdPathInput } from 'form/Inputs';
35const PATH_OPTIONS = [
36 { text: 'Ledger Live', value: LEDGER_LIVE_PATH },
37 { text: 'Ledger Legacy', value: LEDGER_LEGACY_PATH },
38];
40const ACCOUNT_OPTIONS = times(20, i => ({
41 text: `Account #${i + 1}`,
42 value: i,
43}));
45export default function Ledger({ className, goHome }) {
46 useLoginView(WALLET_TYPES.LEDGER);
48 const { setWallet, setWalletHdPath } = useWallet();
50 const validate = useMemo(
51 () =>
52 composeValidator({
53 useCustomPath: buildCheckboxValidator(),
54 derivationpath: buildSelectValidator(PATH_OPTIONS),
55 account: buildSelectValidator(ACCOUNT_OPTIONS),
56 hdpath: buildHdPathValidator(),
57 }),
58 []
59 );
61 const onSubmit = useCallback(
62 async values => {
63 const transport = await Transport.create();
64 const eth = new Eth(transport);
65 const path = chopHdPrefix(values.hdpath);
67 try {
68 const info = await eth.getAddress(path, false, true);
69 const publicKey = Buffer.from(info.publicKey, 'hex');
70 const chainCode = Buffer.from(info.chainCode, 'hex');
71 const pub = secp256k1.publicKeyConvert(publicKey, true);
72 const hd = bip32.fromPublicKey(pub, chainCode);
74 setWallet(Just(hd));
75 setWalletHdPath(addHdPrefix(values.hdpath));
76 } catch (error) {
77 return { [FORM_ERROR]: error.message };
78 }
79 },
80 [setWallet, setWalletHdPath]
81 );
83 const onValues = useCallback(({ valid, values, form }) => {
84 if (!valid) {
85 return;
86 }
88 // when the base path pattern or the account index changes
89 // update the hd path in our input
90 if (!values.useCustomPath) {
91 form.change(
92 'hdpath',
93 values.derivationpath.replace(/x/g, values.account)
94 );
95 }
96 }, []);
98 const initialValues = useMemo(
99 () => ({
100 useCustomPath: false,
101 hdpath: PATH_OPTIONS[0].value.replace(/x/g, ACCOUNT_OPTIONS[0].value),
102 derivationpath: PATH_OPTIONS[0].value,
103 account: ACCOUNT_OPTIONS[0].value,
104 }),
105 []
106 );
108 const full = useBreakpoints([true, true, false]);
109 const half = useBreakpoints([false, false, true]);
110 const isHTTPS = document.location.protocol === 'https:';
112 // when not on https, tell user how to get there
113 const renderHTTP = () => (
114 <>
115 <Grid.Item full as={H5}>
116 Running on HTTP?
117 </Grid.Item>
119 <Grid.Item full as={P}>
120 To authenticate and sign transactions with a Ledger, Bridge must be
121 serving over HTTPS on localhost. You can do this via the following:
122 </Grid.Item>
124 <Grid.Item full as="ol">
125 <li>
126 Install{' '}
127 <a
128 target="_blank"
129 href="https://github.com/FiloSottile/mkcert"
130 rel="noopener noreferrer">
131 mkcert
132 </a>
133 </li>
134 <li className="mt3">
135 Install a local certificate authority via <code>mkcert -install</code>
136 </li>
137 <li className="mt3">
138 In your <code>bridge</code> directory, generate a certificate valid
139 for localhost via <code>mkcert localhost</code>. This will produce two
140 files: <code>localhost.pem</code>, the local certificate, and
141 <code>localhost-key.pem</code> , its corresponding private key.
142 </li>
143 <li className="mt3">
144 Run <code>python bridge-https.py</code>
145 </li>
146 <li className="mt3">Return to this page.</li>
147 </Grid.Item>
148 </>
149 );
151 const renderHTTPS = () => (
152 <>
153 <Grid.Item full as={H5}>
154 Authenticate With Your Ledger
155 </Grid.Item>
156 <Grid.Item full as={Text} className="f6 mb3">
157 Connect and authenticate to your Ledger, and then open the "Ethereum"
158 application. If you're running on older firmware, make sure the "browser
159 support" option is turned on. To sign transactions, you'll also need to
160 enable the "contract data" option.
161 </Grid.Item>
163 <BridgeForm
164 validate={validate}
165 onValues={onValues}
166 onSubmit={onSubmit}
167 afterSubmit={goHome}
168 initialValues={initialValues}>
169 {({ handleSubmit }) => (
170 <>
171 <Condition when="useCustomPath" is={true}>
172 <Grid.Item full as={HdPathInput} name="hdpath" label="HD Path" />
173 </Condition>
175 <Condition when="useCustomPath" is={false}>
176 <Grid.Item
177 full={full}
178 half={half && 1}
179 as={SelectInput}
180 className={cn({ pr1: half })}
181 name="derivationpath"
182 label="Derivation Path"
183 placeholder="Choose path pattern..."
184 options={PATH_OPTIONS}
185 />
187 <Grid.Item
188 full={full}
189 half={half && 2}
190 as={SelectInput}
191 className={cn({ pl1: half })}
192 name="account"
193 label="Account"
194 placeholder="Choose account..."
195 options={ACCOUNT_OPTIONS}
196 />
197 </Condition>
199 <Grid.Item
200 full
201 as={CheckboxInput}
202 className="mv3"
203 name="useCustomPath"
204 label="Custom HD Path"
205 />
207 <Grid.Item full as={FormError} />
209 <Grid.Item full as={SubmitButton} handleSubmit={handleSubmit}>
210 Authenticate
211 </Grid.Item>
212 </>
213 )}
214 </BridgeForm>
215 </>
216 );
218 return (
219 <Grid className={className}>{isHTTPS ? renderHTTPS() : renderHTTP()}</Grid>
220 );